Mo team algorithm --- elegant violence --- natural algorithm

Posted by kratos-2012 on Wed, 06 Oct 2021 21:47:35 +0200

Mo team algorithm - elegant violence

Mo team - elegant violence. When encountering a large number of interval queries, if the left and right subscripts of the interval have a certain law, how can we solve it?

For example:

[ 1 , 3 ] , [ 1 , 4 ] , [ 1 , 5 ] , [ 2 , 5 ] [1,3],[1,4],[1,5],[2,5] [1,3],[1,4],[1,5],[2,5].

Double pointer, of course! Its time complexity is from n 2 n^2 n2 drops to o ( n ) o(n) o(n).

Mo team is such an algorithm, which reduces the time complexity by preprocessing the query order.

Of course, the premise is that it can be preprocessed, and the problem of forced online will be missed by Mo team.

The pretreatment process is as follows:

  • First, divide the data into blocks, and the block size is size
  • Sort the intervals. If the left end point of the interval falls in the same block, we sort it by the size of the right end point.
  • If their left endpoints are not in the same block, they are sorted in ascending order according to the left endpoints.

When we finish processing the interval order, all that remains is the left and right movement of the double pointer. Considering the impact of addition and deletion on the answer, it is over.

ll sum = 0;
	s[a[1]]++;
	for (int i = 1; i <= m; i++)
	{
		while (l < q[i].l)sum += del(l++);
		while (l > q[i].l)sum += add(--l);
		while (r < q[i].r)sum += add(++r);
		while (r > q[i].r)sum += del(r--);
		ans[q[i].id] = sum;
	}

So the problem is, how to analyze the time complexity of Mo team algorithm?

Consider asking about a single block. We consider the worst case. The left end point in the same block jumps repeatedly, first to the left end of the block, and then to the right end. Of course, the right end point is orderly, and it only moves backward. So the time complexity is o ( s i z e ∗ m i + n ) o(\sqrt{size}*m_i+n) o(size * mi + n), where m i m_i mi , represents the interval number in block i

Then the overall time complexity is o ( s i z e ∗ m + n ∗ ( n s i z e ) ) o(\sqrt{size}*m+n*(\frac{n}{size})) o(size ​∗m+n∗(sizen​))​.

Draw a key parity optimization

Let's think about the double pointer of Mo team before optimization. The left pointer does not need to jump horizontally in the block, while the right pointer moves from left to right until the last one in the block.

When reaching another block, the right pointer needs to return to the minimum, but it is not needed. This is the place of parity optimization.

We can sort odd blocks in ascending order by the right pointer and even blocks in descending order, so we reduce the regression of the right pointer, which is a natural, effective and beautiful optimization.

The following is the board question of Mo team

Luogu P1494

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 5e4 + 5;
struct node {
	int l, r, id;
}q[maxn];
ll n, m, a[maxn], ans[maxn], l = 1, r = 1, sum, s[maxn], id[maxn], Size;
ll lef[maxn], righ[maxn];
bool cmp(struct node x, struct node y)
{
	if (id[x.l] == id[y.l])
	{
		if(id[x.l]&1)return x.r < y.r;
		return x.r>y.r;
	}//Sorting: the left end points are in the same block, sorted in ascending order according to the right interval, and optimized by odd and even numbers
	return x.l < y.l;//Otherwise, sort by left interval 
}
ll add(int x)
{
	ll gs = ++s[a[x]];
	return s[a[x]] * (s[a[x]] - 1) / 2 - (s[a[x]] - 1)*(s[a[x]] - 2) / 2;//Return impact
}
ll del(int x)
{
	ll gs = --s[a[x]];
	return gs * (gs - 1) / 2 - gs * (gs + 1) / 2;//Return impact
}
ll gcd(ll a, ll b)
{
	if (a == 0)return b>0?b:1;
	return b == 0 ? a : gcd(b, a%b);
}
int main()
{
	ios::sync_with_stdio(false);
	//freopen("P1494_1.in", "r+", stdin);
	cin >> n >> m;
	Size = n / sqrt(m * 2 / 3);//The block size will affect the time complexity.
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		id[i] = (i - 1) / Size + 1;
	}
	for (int i = 1; i <= m; i++)
	{
		cin >> q[i].l >> q[i].r;
		q[i].id = i;
		lef[i] = q[i].l;
		righ[i] = q[i].r;
	}
	sort(q + 1, q + m + 1, cmp);
	ll sum = 0;
	s[a[1]]++;
	for (int i = 1; i <= m; i++)
	{
		while (l < q[i].l)sum += del(l++);
		while (l > q[i].l)sum += add(--l);
		while (r < q[i].r)sum += add(++r);
		while (r > q[i].r)sum += del(r--);
		ans[q[i].id] = sum;
	}
	//cout << m << endl;
	for (int i = 1; i <= m; i++)
	{
		ll gs = righ[i] - lef[i] + 1;
		if (gs == 1) { cout << "0/1" << endl; continue; }
		ll g=gcd(ans[i], gs*(gs - 1) / 2);
		//cout <<i<<" "<< ans[i]<<" "<<g << endl;
		cout<<  ans[i] / g << '/' << gs * (gs - 1) / 2 / g << endl;
	}
	return 0;
}

codeforces

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+6;
#define ll long long
ll a[maxn],cnt=0,buc1[maxn],buc2[maxn],id[maxn],ans[maxn];
int Size;
struct mo
{
	int l,r;
	int pos;
	bool operator <(const struct mo x)const
	{
		if(id[l]==id[x.l])
        {
            if(id[l]&1)return r<x.r;
            return r>x.r;
        }
		return l<x.l;
	}
}q[maxn];
int main()
{
	int n,m,k;
	scanf("%d%d%d",&n,&m,&k);
	Size=(int)sqrt(n);
	for(int i=1;i<=n;i++)
	{
		//cin>>a[i];
		scanf("%lld",&a[i]);
		a[i]^=a[i-1];
		id[i]=(i-1)/Size+1;
	}
	//for(int i=1;i<=n;i++)cout<<a[i]<<" ";cout<<endl;
	for(int i=1;i<=m;i++)
	{
		//cin>>q[i].l>>q[i].r;
		scanf("%lld%lld",&q[i].l,&q[i].r);
		q[i].pos=i;
	}
	sort(q+1,q+m+1);
	ll l=1,r=1,cnt=0;
	buc1[0]=1;
	buc2[a[1]]=1;
	if(a[1]==k)cnt++;
	for(int i=1;i<=m;i++)
	{
		//cout<<q[i].pos<<endl; 
		while(r<q[i].r)
		{	
			buc1[a[r]]++;buc2[a[r+1]]++;
			cnt+=buc1[a[r+1]^k];
		//	if(buc1[a[r+1]^k]>0)cout<<i<<" "<<l<<" "<<r+1<<" +"<<buc1[a[r+1]^k]<<endl;
			r++;
			
		}
		while(r>q[i].r)
		{	
			
			cnt-=buc1[a[r]^k];
		//	if(buc1[a[r]^k]>0)cout<<i<<" "<<l<<" "<<r-1<<" -"<<buc1[a[r]^k]<<endl;
			buc1[a[r-1]]--;buc2[a[r]]--;
			r--;
		}
		while(l<q[i].l)
		{
			cnt-=buc2[a[l-1]^k];
		//	if(buc2[a[l-1]^k]>0)cout<<i<<" "<<l+1<<" "<<r<<" -"<<buc2[a[l-1]^k]<<endl;
			buc1[a[l-1]]--;buc2[a[l]]--;
			l++;
		}
		while(l>q[i].l)
		{	
			buc2[a[l-1]]++;
			cnt+=buc2[a[l-2]^k];
		//	if(buc2[a[l-2]^k]>0)cout<<i<<" "<<l-1<<" "<<r<<" +"<<buc2[a[l-2]^k]<<endl;
			l--;
			buc1[a[l-1]]++;
		}
		ans[q[i].pos]=cnt;
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	return 0;
}

2021Niuke National Day

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=2e6+6;
#define scf(x) scanf("%d",&x)
int pos[maxn],a[maxn],Size,buc[maxn],cnt,ans[maxn];
struct mo
{
    int l,r;
    int id;
    bool operator <(const struct mo y)const
    {
        if(pos[l]==pos[y.l])
        {
            if(pos[l]&1)return r<y.r;
            return r>y.r;                
        }
        return l<y.l;
    }
}q[maxn];
inline void add(int x)
{
    if(buc[a[x]]==0)cnt++;
    buc[a[x]]++;
}
inline void del(int x)
{
    if(buc[a[x]]==1&&x!=0)cnt--;
    buc[a[x]]--;
}
int main()
{
    int n,m;
    while(~scanf("%d %d",&n,&m)){
        Size=sqrt(n);
        cnt=0;
        for(int i=1;i<=n;i++)scf(a[i]),pos[i]=(i-1)/Size+1,buc[i]=0;
        for(int i=1;i<=n;i++)
        {
            if(buc[a[i]]==0)cnt++;
            buc[a[i]]++;
        }
       // cout<<cnt<<endl;
        for(int i=1;i<=m;i++)scf(q[i].l),scf(q[i].r),q[i].id=i;
        sort(q+1,q+m+1);
        int l=0,r=0;
        for(int i=1;i<=m;i++)
        {
            while(r<q[i].r)del(r++);
            while(r>q[i].r)add(--r);
            while(l<q[i].l)add(++l);
            while(l>q[i].l)del(l--);
            ans[q[i].id]=cnt;
        }
        for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
    }
    return 0;
}

Topics: Algorithm data structure