zoukankan      html  css  js  c++  java
  • 切树游戏(题解)

    还是打上转载吧,毕竟很多都是shadowice1984的东西,只是我用了我看得懂的语言(外星语言?)解释了一遍。ZFY:震惊,机房猴子不会普通话

    前言

    这道题目真的难!!!才紫的!!!

    参考文献

    总体框架:https://www.luogu.com.cn/blog/ShadowassIIXVIIIIV/solution-p3781
    看懂DDP那一块的东西:https://blog.csdn.net/weixin_30258901/article/details/99187239

    例题

    例题

    术语

    (u.lightson)(u)的轻儿子。(u.heavyson)(u)的重儿子。

    思路

    老老实实抄袭了,这个题解我是真的佛了。

    朴素DP

    (dp_{i,j})表示最高点为(i)异或和为(j)的联通块个数。

    那么如果询问为(k)的话我们最后求的就是:(sumlimits_{i=1}^ndp_{i,k})

    推转移方程:
    考虑现在往(dp_{u,k})加入子树(dp_{v})
    (dp_{u,k}+=sumlimits_{i⊕j==k}dp_{u,i}dp_{v,j})

    后面的不就是异或卷积吗?FWT套上。

    然后我们设(Dp_{i})(dp_{i})经过FWT摧残变换后的数组。(注意,基本上后面的值都是FWT向量了。

    所以就是:(Dp_{u,k}=Dp_{u,k}Dp_{v,k}+Dp_{u,k})

    就是:

    (Dp_{u,k}*=(Dp_{v,k}+1))

    DDP加速

    然后我们假如设(这里的(1)表示的是全部位置都是(1)的一个数组,(F,f)也是个数组):
    (F_{u}=prodlimits_{v∈u.son}(F_{u}+1))
    (f_{u}=prodlimits_{v∈u.lightson}(F_{u}+1))

    其实这里的(F)就是上面的(Dp)数组,简写。

    再设(G)数组表示一个节点子树内的答案,(g)表示一个节点轻儿子内的答案

    (g_{x}=sumlimits_{y∈x.lightson}G_{y})

    怎么转移呢?

    (y=x.heavyson)
    (F_{x}=f_{x}*(F_{y}+1))
    (G_{x}=g_{x}+F_{x}+G_{y})

    拆开就是:

    (F_{x}=f_{x}+f_{x}*F_{x})
    (G_{x}=g_{x}+f_{x}*F_{y}+f_{x}+G_{y})

    写成矩阵就是:
    (egin{bmatrix}F_{y} & G_{y} & 1end{bmatrix}*egin{bmatrix}f_{x} & f_{x} & 0 \ 0 & 1 & 0\ f_{x} & f_{x}+g_{x} & 1end{bmatrix}=egin{bmatrix}F_{x} & G_{x} & 1end{bmatrix})

    但是这个矩阵特别难维护。

    怎么搞?

    奆佬猫神给了我们一个神仙的维护方法:

    (egin{bmatrix}a & b & 0 \ 0 & 1 & 0\ c & d & 1end{bmatrix}*egin{bmatrix}A & B & 0 \ 0 & 1 & 0\ C & D & 1end{bmatrix}=egin{bmatrix}aA & aB+b & 0 \ 0 & 1 & 0\ cA+C & cB+d+D & 1end{bmatrix})

    维护四个向量就可以啦!!!

    当然也许有人会问单位矩阵是什么,又没有改变矩阵乘法的方式,不就是:(a=1,b=c=d=0)

    细节

    关于修改是如何逆回去的

    这里有模运算,可以大量的逆元乘法,但是也是因为模运算,导致有可能乘的是0,出现了乘(0)(0)的情况,那么我们就只能坐以待毙了吗,其实不然,在维护(f)的时候,我们可以顺带记录乘了多少个(0),实现不可实现的除以(0)的操作。

    点值都去哪了?

    这里解释一下,点值直接在(FWT)之后,直接赋给了(f),自己看看就会发现不是很影响。

    矩阵的答案以及如何从轻儿子的矩阵抠东西下来

    我们知道了一个矩阵,我们又要怎么把他的答案抠下来?

    其实就是乘一个(egin{bmatrix}0 & 0 & 1end{bmatrix})就全部抠下来了。

    轻儿子你也可以看作乘了(egin{bmatrix}0 & 0 & 1end{bmatrix})再抠。

    代码

    时间复杂度:(O(mqlog{n}))

    #include<cstdio>
    #include<cstring> 
    #define  N  31000
    #define  NN  61000
    #define  M  140
    #define  mod  10007
    using  namespace  std;
    int  inv[N];
    int  lim,n;
    struct  node
    {
    	int  y,next;
    }tr[NN];int  last[N],len;
    inline  void  inz(int  x,int  y){len++;tr[len].y=y;tr[len].next=last[x];last[x]=len;}
    inline  void  fwt(int  *a,const  int&  o)
    {
    	for(register  int  i=2;i<=lim;i<<=1)
    	{
    		int  mid=(i>>1);
    		for(register  int  j=0;j<lim;j+=i)
    		{
    			for(register  int  k=0;k<mid;k++)
    			{
    				int  x=a[j+k],y=a[j+k+mid];
    				a[j+k]=(x+y)*o%mod;a[j+k+mid]=(x-y+mod)*o%mod;//当初为了减轻常数抄了题解 
    			}
    		}
    	}
    }//FWT 
    struct  poly
    {
    	int  a[M];
    	inline  int&  operator[](const  int&  x){return  a[x];}
    }we[N],one,L2;
    inline  int  pdz(int  x){return  x>mod?x-mod:x;}
    inline  int  pdf(int  x){return  x<0?x+mod:x;}
    inline  poly  operator*(poly  x,poly  y){for(register  int  i=0;i<lim;i++)L2[i]=(x[i]*y[i])%mod;return  L2;}
    inline  poly  operator+(poly  x,poly  y){for(register  int  i=0;i<lim;i++)L2[i]=pdz(x[i]+y[i]);return  L2;}
    inline  poly  operator-(poly  x,poly  y){for(register  int  i=0;i<lim;i++)L2[i]=pdf(x[i]-y[i]);return  L2;}
    inline  poly  operator/(poly  x,poly  y){for(register  int  i=0;i<lim;i++)L2[i]=L2[i]*(inv[y[i]])%mod;return  L2;}
    inline  void  operator+=(poly  &x,poly  y){for(register  int  i=0;i<lim;i++)x[i]=pdz(x[i]+y[i]);}
    inline  void  operator-=(poly  &x,poly  y){for(register  int  i=0;i<lim;i++)x[i]=pdf(x[i]-y[i]);}
    inline  void  operator*=(poly  &x,poly  y){for(register  int  i=0;i<lim;i++)x[i]=(x[i]*y[i])%mod;}
    inline  void  operator/=(poly  &x,poly  y){for(register  int  i=0;i<lim;i++)x[i]=x[i]*(inv[y[i]])%mod;}
    struct  Mar
    {
    	poly  a[2][2];
    	poly*  operator[](const  int&  x){return  a[x];}
    }L1;//矩阵 
    inline  Mar  operator*(Mar  a,Mar  b)
    {
    	L1[0][0]=a[0][0]*b[0][0];
    	L1[0][1]=a[0][0]*b[0][1]+a[0][1];
    	L1[1][0]=a[1][0]*b[0][0]+b[1][0];
    	L1[1][1]=a[1][0]*b[0][1]+a[1][1]+b[1][1];
    	return  L1;
    }
    struct  data//维护f的工具。 
    {
    	int  fz[M];//有多少个0 
    	int  fv[M];//除了0以外的乘积,没有默认为1 
    	inline  void  wt(const  int  x,const  int  k){if(k==0)fz[x]=fv[x]=1;else  fz[x]=0,fv[x]=k;} //初始化所用到的函数 
    	inline  void  operator*=(poly  y){for(int  i=0;i<lim;i++)y[i]%mod==0?fz[i]+=1:fv[i]=(fv[i]*y[i])%mod;}
    	inline  void  operator/=(poly  y){for(int  i=0;i<lim;i++)y[i]%mod==0?fz[i]-=1:fv[i]=(fv[i]*inv[y[i]])%mod;}
    };
    inline  void  cpy(poly  &x,const  data  y){for(int  i=0;i<lim;i++)x[i]=(!y.fz[i])?y.fv[i]:0;}//将f复制到矩阵上面的一个向量上 
    struct  Tree
    {
    	int  son[N][2],h[N],size[N],fa[N];
    	data  lt[N];/*f*/poly  lh[N];/*g*/
    	Mar  val[N]/*BST上所管理的值*/,w[N]/*自身的值*/;
    	bool  v[N];//用来建全局平衡二叉树的 
    	inline  void  update(const  int  x){val[x]=val[son[x][0]]*w[x]*val[son[x][1]];}//维护
    	inline  void  ins(const  int  x,const  int  y){lt[x]*=(val[y][1][0]+one);lh[x]+=val[y][1][1];}//将一个点插入到x中 
    	inline  void  del(const  int  x,const  int  y){lt[x]/=(val[y][1][0]+one);lh[x]-=val[y][1][1];}//将一个点删除出x 
    	inline  bool  pd(const  int  x){return  son[fa[x]][0]!=x  &&  son[fa[x]][1]!=x;}//判断是不是真正的二组 
    	inline  void  pu(const  int  x){cpy(w[x][0][0],lt[x]);w[x][0][1]=w[x][1][0]=w[x][0][0];w[x][1][1]=lh[x]+w[x][0][0];}//将一个点的f,g打到矩阵上面去 
    	int  sta[N],top;
    	int  root;poly  ans;
    	int  bt(int  l,int  r)
    	{
    		if(l>r)return  0;
    		int  sum=0;
    		for(register  int  i=l;i<=r;i++)sum+=size[sta[i]];
    		int  now=size[sta[l]];
    		for(register  int  i=l;i<=r;i++,now+=size[sta[i]])
    		{
    			int  x=sta[i];
    			if(now*2>=sum)
    			{
    				int  ls=bt(i+1,r),rs=bt(l,i-1);fa[ls]=fa[rs]=x;
    				son[x][0]=ls;son[x][1]=rs;
    				update(x);
    				return  x;
    			}
    		}
    	}
    	int  sbuild(int  x)
    	{
    		for(register  int  i=x;i;i=h[i])v[i]=1;
    		for(register  int  i=x;i;i=h[i])
    		{
    			for(int  k=last[i];k;k=tr[k].next)
    			{
    				int  y=tr[k].y;
    				if(!v[y])
    				{
    					int  z=sbuild(y);fa[z]=i;
    					ins(i,z);
    				}
    			}
    		}
    		top=0;for(register  int  i=x;i;i=h[i])pu(i),sta[++top]=i,size[i]-=size[h[i]];
    		return  bt(1,top);
    	}
    	void  dfs(int  x,const  int  fa)
    	{
    		size[x]=1;
    		for(register  int  k=last[x];k;k=tr[k].next)
    		{
    			int  y=tr[k].y;
    			if(fa!=y)
    			{
    				dfs(y,x);
    				size[x]+=size[y];
    				if(size[y]>size[h[x]])h[x]=y;
    			}
    		}
    	}
    	//建树部分
    	inline  void  modify(int  x,int  k)
    	{
    		lt[x]/=we[x];
    		for(register  int  i=0;i<lim;i++)we[x][i]=0;we[x][k]=1;
    		fwt(we[x].a,1); 
    		lt[x]*=we[x];pu(x);
    		//先删后加
    		for(register  int  i=x;i;i=fa[i])
    		{
    			if(pd(i)  &&  fa[i]){del(fa[i],i);update(i);ins(fa[i],i);pu(fa[i]);}
    			else  update(i);
    		}//沿着重链或者轻链慢慢往上跳 
    	}
    	inline  void  getans()
    	{
    		ans=val[root][1][1];//答案 
    		fwt(ans.a,5004);
    	}
    	
    }T;
    int  main()
    {
    	inv[1]=1;for(register  int  i=2;i<mod;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;//线性推逆元 
    	scanf("%d%d",&n,&lim);
    	for(register  int  i=0;i<lim;i++)one[i]=1;
    	for(register  int  i=1;i<=n;i++){int  x;scanf("%d",&x);we[i][x]=1;fwt(we[i].a,1);}
    	for(register  int  i=1;i<n;i++)
    	{
    		int  x,y;scanf("%d%d",&x,&y);
    		inz(x,y);inz(y,x);
    	}
    	T.w[0][0][0]=T.val[0][0][0]=one;//处理成单位矩阵 
    	for(int  i=1;i<=n;i++)for(int  j=0;j<lim;j++)T.lt[i].wt(j,we[i][j]);//预处理 
    	T.dfs(1,0);T.root=T.sbuild(1);T.getans();
    	int  q;scanf("%d",&q);
    	for(register  int  i=1;i<=q;i++)
    	{
    		register  char  opt[10];scanf("%s",opt);
    		if(opt[0]=='Q')
    		{
    			int  t;scanf("%d",&t);
    			printf("%d
    ",T.ans[t]);
    		}
    		else  {int  x,k;scanf("%d%d",&x,&k);T.modify(x,k);T.getans();}
    	}
    	return  0;
    }
    
  • 相关阅读:
    lmdb简介——结合MVCC的B+树嵌入式数据库
    influxdb和boltDB简介——MVCC+B+树,Go写成,Bolt类似于LMDB,这个被认为是在现代kye/value存储中最好的,influxdb后端存储有LevelDB换成了BoltDB
    时序列数据库选型
    VoltDB介绍——本质:数据保存在内存,充分利用CPU,单线程去锁,底层数据结构未知
    关于时间序列数据库的思考——(1)运用hash文件(例如:RRD,Whisper) (2)运用LSM树来备份(例如:LevelDB,RocksDB,Cassandra) (3)运用B-树排序和k/v存储(例如:BoltDB,LMDB)
    241. Different Ways to Add Parentheses——本质:DFS
    麦克风阵列技术入门(3)
    [LeetCode]Palindrome Partitioning 找出所有可能的组合回文
    Linux在简短而经常使用的命令
    数据结构c字符串操作语言版本
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/12187591.html
Copyright © 2011-2022 走看看