[6题大章] 莫队、带修莫队、树上莫队学习笔记

大佬ouuan学习笔记:莫队、带修莫队、树上莫队详解

这个写的很好,有适用范围 与 算法思想
       \text{ \ \ \ \ }       

普通莫队

时间复杂度证明:
左端点都在同一个 n \sqrt{n} n
左端点最多移 O ( m n ) O(m\sqrt{n}) O(mn ), 右端点升序排序,对于每一块最多移到底,即 O ( n n ) O(n\sqrt{n}) O(nn )
总复杂度 O ( ( m + n ) n + m l o g 2 m ) O((m+n)\sqrt{n} + mlog_{2}{m}) O((m+n)n +mlog2m)
对于每一道题,随要求改变统计的数据即可
       \text{ \ \ \ \ }       
       \text{ \ \ \ \ }       

背板子重点:

1.分块按照 n \sqrt{n} n
2.不必按块枚举,询问区间属于哪一块的信息记录在结构体内排序
3.排序先按块升序,再按右端点升序
3. l = 1 , r = 0 l=1,r=0 l=1,r=0 防止(1,1)无法记录,所以r=0
       \text{ \ \ \ \ }       
       \text{ \ \ \ \ }       
       \text{ \ \ \ \ }       

P2709 小B的询问

统计    t [ a [ i ] ] 2    \text{\ \ }t[a[i]]^{2}\text{\ \ }   t[a[i]]2  ,由    t [ a [ i ] ] 2    \text{\ \ }t[a[i]]^{2}\text{\ \ }   t[a[i]]2   ( t [ a [ i ] ] + 1 ) 2 (t[a[i]]+1)^{2} t[a[i]]+12,需要加减 2 × t [ a [ i ] ] + 1 2\times t[a[i]]+1 2×t[a[i]]+1

#include<cstdio>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
int a[51000],t[51000];//t[i]数i出现次数 
struct node{int x,y,i,d;}st[51000]; //区间左右范围 ;属于哪个块   最大50000^2 25亿->long long 
bool cmp(node a,node b) 
{
	if(a.d==b.d)  return a.y<b.y;
	return a.d<b.d;
}LL ans=0,an[51000];
int main()
{
	int n,m,k;
	scanf("%d %d %d",&n,&m,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	int q=sqrt(n); 
	for(int i=1;i<=m;i++){
		scanf("%d %d",&st[i].x,&st[i].y);
		st[i].i=i, st[i].d= (st[i].x-1)/q + 1; //分块 
	}
	sort(st+1,st+1+m,cmp);	
	int l=1,r=0; //防止1 1无法记录,所以r=0  
	for(int i=1;i<=m;i++)
	{//直接左右端点移动即可,已经按排最坏n*sqrt(n)顺序 
		//左端点最多  m* sqrt(n)  ,右端点每块最多移到低(即n) sqrt(n)*n 
		while(l>st[i].x) l--, t[a[l]]++, ans+=2*t[a[l]]-1;  //(x)^2-(x-1)^2 =2*x-1
        while(r<st[i].y) r++, t[a[r]]++, ans+=2*t[a[r]]-1;
        while(l<st[i].x) t[a[l]]--, ans-=2*t[a[l]]+1, l++;
        while(r>st[i].y) t[a[r]]--, ans-=2*t[a[r]]+1, r--;
        an[st[i].i]=ans;
	}for(int i=1;i<=m;i++) printf("%lld\n",an[i]);
	return 0;
}

P1494 [国家集训队]小Z的袜子

统计 C t [ i ] 2 = t [ i ] × t [ i − 1 ] / 2 C^{2}_{t[i]}=t[i]\times t[i-1] /2 Ct[i]2=t[i]×t[i1]/2
每多一只同色袜子 t ∗ ( t − 1 ) / 2 − ( t − 1 ) ∗ ( t − 2 ) / 2 = 1 / 2 ∗ ( t − 1 ) ∗ ( 2 ) = t − 1 t*(t-1)/2 - (t-1)*(t-2)/2 =1/2 * (t-1) *(2) =t-1 t(t1)/2(t1)(t2)/2=1/2(t1)(2)=t1

#include<cstdio>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
int a[51000],t[51000];//t[i]数i出现次数 
struct node{int x,y,i,d;}st[51000]; 
bool cmp(node a,node b) 
{
	if(a.d==b.d)  return a.y<b.y;
	return a.d<b.d;
}LL ans=0,anx[51000],any[51000];
LL gcd(LL a,LL b){
	if(b==0) return a;
	return gcd(b,a%b);
}
int main()
{
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	int q=sqrt(n); 
	for(int i=1;i<=m;i++){
		scanf("%d %d",&st[i].x,&st[i].y) ;
		st[i].i=i,st[i].d=(st[i].x-1)/q + 1; //分块 
	}
	sort(st+1,st+1+m,cmp);	
	int l=1,r=0; //防止1 1无法记录,所以r=0  
	for(int i=1;i<=m;i++)
	{
		LL L=st[i].y-st[i].x+1;//C(L,2); t*(t-1)/2 - (t-1)*(t-2)/2 = 1/2 * (t-1) *(2) =t-1 多一只袜子 
		if(st[i].x==st[i].y) {anx[st[i].i]=0;any[st[i].i]=1; continue;}
		
		while(l>st[i].x) l--, t[a[l]]++, ans+=t[a[l]]-1;  
        while(r<st[i].y) r++, t[a[r]]++, ans+=t[a[r]]-1;
        while(l<st[i].x) ans-=t[a[l]]-1, t[a[l]]--  ,l++;
        while(r>st[i].y) ans-=t[a[r]]-1, t[a[r]]--  ,r--;
        
        anx[st[i].i]=ans;any[st[i].i]=L*(L-1)/2;       
	}
	for(int i=1;i<=m;i++){
		LL g=gcd(anx[i],any[i]);
		printf("%lld/%lld\n",anx[i]/g,any[i]/g);
	}
	return 0;
}

P3901 数列找不同

tot记录有几种数超过1
某一种数重复了 t o t + = ( t [ a [ l ] ] = = 2 ) tot+=(t[a[l]]==2) tot+=(t[a[l]]==2)
某一种数不再重复了 t o t − = ( t [ a [ l ] ] = = 1 ) tot-=(t[a[l]]==1) tot=(t[a[l]]==1)

#include<cstdio>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
int a[100100],t[100100];//t[i]数i出现次数 
struct node{int x,y,i,d;}st[100100]; 
bool cmp(node a,node b) 
{
	if(a.d==b.d)  return a.y<b.y;
	return a.d<b.d;
}bool an[100100];
int main()
{
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	int q=sqrt(n); 
	for(int i=1;i<=m;i++){
		scanf("%d %d",&st[i].x,&st[i].y) ;
		st[i].i=i,st[i].d=(st[i].x-1)/q + 1; //分块 
	}
	sort(st+1,st+1+m,cmp);	
	int l=1,r=0,tot=0; //防止1 1无法记录,所以r=0  
	for(int i=1;i<=m;i++)
	{//tot记录有几种数超过1 
		while(l>st[i].x) l--, t[a[l]]++, tot+=(t[a[l]]==2)?1:0;  
        while(r<st[i].y) r++, t[a[r]]++, tot+=(t[a[r]]==2)?1:0;  
        while(l<st[i].x) t[a[l]]-- ,tot-=(t[a[l]]==1)?1:0   ,l++;
        while(r>st[i].y) t[a[r]]-- ,tot-=(t[a[r]]==1)?1:0   ,r--;
        if(tot==0) an[st[i].i]=true;
        else an[st[i].i]=false;
	}
	for(int i=1;i<=m;i++) if(an[i]) printf("Yes\n");else printf("No\n"); 
	return 0;
}

       \text{ \ \ \ \ }       
       \text{ \ \ \ \ }       
       \text{ \ \ \ \ }       

带修莫队

       \text{ \ \ \ \ }       

背板子重点:

1.分块按照 n 2 3 n^{ \frac{2}{3}} n32

int q=pow(n,2.0/3.0);

2.排序按照左端点所在块升序、右端点所在块升序,时间(第几次修改之后)升序

return block[l]==block[b.l]?(block[r]==block[b.r]?t<b.t:r<b.r):l<b.l;

       \text{ \ \ \ \ }       
       \text{ \ \ \ \ }       
       \text{ \ \ \ \ }       

P1903 [国家集训队]数颜色 / 维护队列

修改也当做一维,当前是修改了n次以后的,要求修改k次以后的,那就n-k次朴素改点再统计
这道题卡常极其严重。。。

#include<cstdio>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N=140010,M=1000100;
int q;

int a[N],block[N],t[M];//t[i]数i出现次数 
struct node{
	int l,r,i,t;
	bool operator < (node &b){
        return block[l]==block[b.l]?(block[r]==block[b.r]?t<b.t:r<b.r):l<b.l;
    }
}st[N]; 
/*//不想打在结构体里也可以
bool cmp(node a,node b){
	return block[a.l]==block[b.l]?(block[a.r]==block[b.r]?a.t<b.t:a.r<b.r):a.l<b.l;
}*/
struct nod{int p,col;}ch[N]; 
int tot=0;
inline void add(int x){
    if(t[x]++==0) ++tot;
}
inline void del(int x){
    if(--t[x]==0) --tot;
}
inline int read(){
	int x=0;char c=getchar();
	while(c<48) c=getchar();
	while(c>47) x=x*10+c-'0',c=getchar();
	return x;
}
inline void modify(int x,int ti)
{//这里仅执行退一步 
    if(ch[ti].p>=st[x].l && ch[ti].p<=st[x].r){
        del(a[ch[ti].p]);
    	add(ch[ti].col);
    }
    swap(a[ch[ti].p],ch[ti].col); //下次执行时必定是回退这次操作,直接互换就可以了 
}
int an[N];
int main()
{
	int n=read(),m=read();q=pow(n,2.0/3.0);
	for(register int i=1;i<=n;i++) 
		a[i]=read(),block[i]=(i-1)/q+1;
		
	int qc=0,cc=0;char p[5]; 
	for(register int i=1;i<=m;i++)
	{
	 	scanf("%s",p);
		if(p[0]=='Q'){
			++qc;
			st[qc].l=read(),st[qc].r=read();
			st[qc].i=qc;st[qc].t=cc;
		}	//记录这次询问是在第几次修改之后(时间)  
		else
			cc++,ch[cc].p=read(),ch[cc].col=read();
	}//如果当前修改数比询问的修改数少就把没修改的进行修改,反之回退。
	sort(st+1,st+1+qc);	
	
	int l=1,r=0,now=0; //防止1 1无法记录,所以r=0  
	for(register int i=1;i<=qc;i++)
	{//tot记录有几种数超过1 
		//printf("id=%d ",st[i].i);
		while(l>st[i].l) add(a[--l]);
		while(r<st[i].r) add(a[++r]);  
        while(l<st[i].l) del(a[l++]);
        while(r>st[i].r) del(a[r--]);
        while(now<st[i].t) modify(i,++now);
        while(now>st[i].t) modify(i,now--);
        an[st[i].i]=tot;
	}
	for(register int i=1;i<=qc;i++) printf("%d\n",an[i]);
	return 0;
}

       \text{ \ \ \ \ }       
       \text{ \ \ \ \ }       

树上莫队

      \text{ \ \ \ }      

方法一:括号序列

P4074 [WC2013]糖果公园

改自洛谷Kelin:

简洁题意:

给你一棵树,每个点有个颜色

每次询问你一条路径求

∑ c v a l c ∑ i = 1 c n t c w o r t h i ∑ \sum_{c}val_c\sum_{i=1}^{cnt_c}worth_i∑ cvalci=1cntcworthi

v a l 表 示 该 颜 色 的 价 值 , c n t 表 示 其 出 现 的 次 数 , w o e t h i val表示该颜色的价值,cnt表示其出现的次数,woeth_i val,cnt,woethi表示第 i i i 次出现的价值

带修改
      \text{ \ \ \ }      

题解:

先求出dfs序把树变成序列
考虑向右扩展一个点,这个贡献我们是可以O(1)算出来的
假设扩展出的点是的颜色是c,那么 Δ = v a l c × w o r t h c n t c + 1 \Delta=val_c\times worth_{cnt_{c+1}} Δ=valc×worthcntc+1
具有莫队性质

      \text{ \ \ \ }      

将树转化成序列:

但是直接用dfs序去扩展的话显然会出问题
因为他会先去扫完起点的子树,产生多余的贡献

所以要用 欧拉序、括号序 把树变成一个长2n序列

这样的话扫的过程中起点的子树里的点肯定会被扫两次(一进一出)
为了连续做两次之后贡献为0,我们可以想到异或

即开一个 v i s vis vis数组,每次访问就一个点u,就 v i s u vis_u visu ^ = 1 =1 =1

但是注意到几个问题

1.如果lca不是路径端点是不会被计算的

在这里插入图片描述
考虑样例的括号序1 3 4 4 2 2 3 1
询问 4 → 2 4 \to 2 42那么我们得到的区间是[3,5]
发现 3 没有被算进来 ,这个要特判

当然如果起点就是lca就不需要管了

2.如果起点不是lca,那么他的贡献是不会被计算的

同样是上面那个例子
我们可以看到 4 的贡献被算两次抵消掉了
所以这种情况也要特判

之后就是带修莫队了。。。

#include<cstdio>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
const int N=100100;
int read(){
	int x=0;char c=getchar();
	while(c<48) c=getchar();
	while(c>47) x=x*10+c-'0',c=getchar();
	return x;
}
struct bian{int y,gg;}b[N<<1];
int first[N],len=0;
void ins(int x,int y){
    b[++len].y=y;
    b[len].gg=first[x];
    first[x]=len;
}

//==============================================================
int c[N],w[N],now[N],v[N],tot[N];
int fa[N][18],dep[N];
int f[N],g[N],id[N<<1],cnt=0;
int block[N<<1];
void dfs(int x)
{	
    f[x]=++cnt; id[cnt]=x;//括号序 
	dep[x]=dep[fa[x][0]]+1;
	for(int i=1;i<=17;i++) fa[x][i]=fa[fa[x][i-1]][i-1];//lca
	
	for(int i=first[x];i>0;i=b[i].gg){
		int y=b[i].y;
		if(y!=fa[x][0]){
			fa[y][0]=x;
			dfs(y);
		}
	}
    g[x]=++cnt;id[cnt]=x;//括号序 
}
int lca(int x,int y)
{
	if(dep[x]>dep[y]) swap(x,y);
	for(int i=17;i>=0;i--)
		if(dep[fa[y][i]]>=dep[x]) y=fa[y][i];
	if(x==y) return x;
	for(int i=17;i>=0;i--)
		if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}//==============================================================

struct node{
	int l,r,i,t;
	bool operator < (node &b){
        return block[l]==block[b.l]?(block[r]==block[b.r]?t<b.t:r<b.r):l<b.l;
    }
}st[N]; 
struct nod{int p,col,last;}ch[N]; 
LL sum=0,ans[N];
bool vis[N];
//===================================================================
void modify(int x)
{
    if(vis[x]) sum-=1ll * v[c[x]] * w[ tot[c[x]]-- ];
    else sum+=1ll * v[c[x]] * w[ ++tot[c[x]] ];
    vis[x]^=1;
}
void change(int x,int y){
    if(vis[x]) {modify(x);c[x]=y;modify(x);}
    else c[x]=y;
}


int main()
{
	int n=read(),m=read(),q=read();
	for(int i=1;i<=m;++i) v[i]=read();
    for(int i=1;i<=n;++i) w[i]=read();
    for(int i=1;i<n;++i){
        int x=read(),y=read();
        ins(x,y);ins(y,x);
    }
    for(int i=1;i<=n;++i) now[i]=c[i]=read();
    dfs(1);
    int B=pow(n,2.0/3.0);
    for(int i=1;i<=cnt;i++) block[i]=(i-1)/B+1;
    
    int qc=0,cc=0;
    for(int i=1;i<=q;i++)
	{
        int p=read();
        if(p){
        	int l=read(),r=read();
            if(f[l]>f[r]) swap(l,r);//括号序从左到右 
            st[++qc].r=f[r]; //取靠左的 
			st[qc].t=cc;st[qc].i=qc;
            st[qc].l=(lca(l,r)==l)?f[l]:g[l];
            //1 3 4 4 2 2 3 1    3只有一个f[3]=2  4要取靠右的g[4]=4,还要记得特判补上3 
        }
        else{
        	int p=read(),col=read();
            ch[++cc].p=p;ch[cc].last=now[p];
            now[p]=ch[cc].col=col;
        }
    }
    
    sort(st+1,st+1+qc);
    int l=1,r=0,t=1;
    for(int i=1;i<=qc;i++)
    { 
		while(t<=st[i].t) change(ch[t].p,ch[t].col),t++;
        while(t>st[i].t) change(ch[t].p,ch[t].last),t--;
        while(l>st[i].l) modify(id[--l]);
		while(r<st[i].r) modify(id[++r]);  
        while(l<st[i].l) modify(id[l++]);
        while(r>st[i].r) modify(id[r--]);
        
        int x=id[l] , y=id[r], tmp=lca(x,y);
        if(x!=tmp && y!=tmp) { modify(tmp); ans[st[i].i]=sum; modify(tmp);}//特判补lca  
        else ans[st[i].i]=sum;
    }
    for(int i=1;i<=qc;++i)
    printf("%lld\n",ans[i]);
	return 0;
}

      \text{ \ \ \ }      
      \text{ \ \ \ }      

方法二:树分块

前置 寻找分块方式

P2325 [SCOI2005]王室联邦

简洁题意:如何分块,使得满足每块大小在 [B,3B],块内每个点到核心点(省会)路径上的所有点都在块内呢?

方式:

1.我们 d f s dfs dfs 整棵树,处理每个节点时,将其一部分子节点分块,将未被分块的子节点返回到上一层。

2.先记录初始栈   s t a   \text{ }sta\text{ }  sta 栈顶高度   t \text{ }t  t , 枚举  x  \text{ x }  x 的每个子节点  y \text{ y}  y,递归处理子树后,将每个子节点返回的未被分块的节点 累叠在栈  sta  \text{ sta }  sta 上 , 一旦 新累加的点数  top-t>=B  \text{ top-t>=B }  top-t>=B 就把 栈 作为  top-t到top  \text{ top-t到top }  top-ttop 的点作为一个新的块并将x作为省会,然后清空。

你会发现:对于最先遍历的一条链, t 都为0 , 而 t 的改变只能是横向的 ,这将保证两两子树之间互不影响,且都能对根做贡献

3.处理完所有子树后,将x也加入到栈中,栈的大小最大为B-1 在加上x节点 所以: 栈大小最大为B

4.对于上一层的子树,最多对 栈 增加 B B B个节点 ( t o p − t > = B 就 弹 出 了 ) (top-t>=B就弹出了) (topt>=B) S 最 多 为 2 B S最多为2B S2B

5.在dfs结束后,S大小最大为B, 随便并入之前的一个块(一般最后一个以根为省会的块),最大 2 B + B 2B+B 2B+B

——改自洛谷Siyuan 与 ouuan

#include<cstdio>
#include<cstring>
using namespace std;
struct bian{int y,gg;}b[2010];
int first[1010],len=0;
void ins(int x,int y){
    b[++len].y=y;
    b[len].gg=first[x];
    first[x]=len;
}
int read(){
	int x=0;char c=getchar();
	while(c<48) c=getchar();
	while(c>47) x=x*10+c-'0',c=getchar();
	return x;
}
int sta[1010],top=0,n,B,tot=0;
int fa[1010],bel[1010],rt[1010];
void dfs(int x){
	int t=top;
	for(int i=first[x];i>0;i=b[i].gg){
		int y=b[i].y;
		if(y!=fa[x])
		{	
			fa[y]=x;dfs(y);
			if(top-t>=B)
			{
				++tot;rt[tot]=x;
				while(top>t) bel[sta[top--]]=tot;
			}
		}
	}sta[++top]=x;
}
int main()
{
	n=read(),B=read();int x,y;
	for(int i=1;i<n;i++) x=read(),y=read(),ins(x,y),ins(y,x);
	dfs(1);
	while(top) bel[sta[top--]]=tot;
	printf("%d\n",tot);
	for(int i=1;i<=n;i++) printf("%d ",bel[i]);
	printf("\n");
	for(int i=1;i<=tot;i++) printf("%d ",rt[i]);
	return 0;
}

   \text{ \ }   
   \text{ \ }   

P4074 [WC2013]糖果公园

我能不打了吗,【哭】。。。
粘个代码。。。
UOJ58 【WC2013】糖果公园

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值