zoukankan      html  css  js  c++  java
  • 并不对劲的长链剖分

    真正强的人

    真正强的人被称为管老师或很对劲的太刀流(点这里

    长链剖分是什么?

    重链剖分是对于每个点,找出它的儿子中子树最大的,称为重(zhong四声)儿子,在它和重儿子之间连重边,其他边是轻边,由重边组成的连是重链
    这样就把一棵树拆成了很多条重链,可以将树上的问题变成链上的问题,时间复杂度会和每个点到根的路径上的轻边数(约log n条)有关
    类似的,长链剖分是对于每个点,找出它的儿子中到叶子节点最远的,称为长(chang二声)儿子,在它和长儿子之间连长边,其他边是短边,由长边组成的连是长链
    这样就把一棵树拆成了很多条长链,它有很多有趣的性质

    用途

    求点(u)(k)级祖先(可跳过)

    讲解

    一般是用倍增,预处理的时间复杂度是(Theta(nspace log space n)),单次询问(Theta(log space n))
    结合长链剖分能做到(Theta(nspace log space n)-Theta(1))
    预处理时除了倍增数组以外,还要对于每条长链的链顶,设链长为(x),则要记它往上走1到(x)个点分别是哪些,往下走1到(x)个点分别是哪些,这一步需要(Theta(n))的时间和空间,因为(Sigma{链长}=n)
    查询时,先从点(u)向上走(2^{highbit(k)})(highbit(x)和lowbit的定义类似,是指x二进制中最高位的位置)步,记跳到的点为(v)
    因为(dis(u,v)=2^{highbit(k)}),所以(v)所在长链的长度肯定大于(2^{highbit(k)}),而从(v)走到(u)(k)级祖先,还需要不到(2^{highbit(k)})步,那么(u)(k)级祖先一定会出现在(v)的链顶记录的区域中
    这个过程是预处理->从(u)向上(2^{highbit(k)})步->看当前点链顶预处理出的区域

    例题

    没有找到例题,可能是因为只有在查询次数较多时这种方法才有优势,而且长链剖分常数比较大,优势不明显
    2.

    优化dp

    讲解

    当dp状态有一维是点,有一维是深度,而且每个点的dp值都是从它的儿子合并来的时,就可以用长链剖分优化了
    每条长链会共用一个数组,在链顶处会将长度为链长的数组合并到另一条链上
    因为(Sigma{链长}=n),所以总空间复杂度是(Theta(n)),时间复杂度会和合并的时间复杂度有关

    例题

    有一棵(n)((nleq2*10^6))个点的树,每个点(i)有点权(w_i)((w_ileq10^9)),若(dis(x,y)=点x到点y的简单路径上有几条边)
    那么求对于所有满足(dis(x,y)=dis(y,z)=dis(x,z)且x eq y且y eq z 且 z eq x)的三元组((x,y,z))(w_x*w_y+w_y*w_z+w_z*w_x)的和是多少

    题解

    暴力的dp:
    (f_0(u,i))表示点(u)的子树中所有点(v)满足(dis(u,v)=k)的点的个数
    (f_1(u,i))表示点(u)的子树中所有点(v)满足(dis(u,v)=k)的点的点权和
    (g_0(u,i))表示点(u)的子树中所有点对((x,y))满足(dis(u,x)=dis(u,y)=dis(x,y)-k)(x eq y)的点对的(点对点权和)之和
    (g_0(u,i))表示点(u)的子树中所有点对((x,y))满足(dis(u,x)=dis(u,y)=dis(x,y)-k)(x eq y)的点对的(点对点权积)之和
    (ans(u))表示以点(u)为LCA的三元组(x,y,z)对答案造成的贡献
    则有:
    (spacef f_0(u,i)=sum_{vin son(u)}{f_0(v,i-1)}(i>0))
    (u)的儿子们的子树中到点(u)的儿子的距离为(i-1)的点显然到点(u)的距离为(i)
    (spacef f_0(u,0)=1)
    (u)的子树中满足(dis(u,v)=0)的点只有(u)自己
    (spacef f_1(u,i)=sum_{vin son(u)}{f_1(v,i-1)}(i>0))
    (f_0)
    (spacef f_1(u,0)=w_u)
    (f_0)
    (spacef g_0(u,i)=sum_{vin son(u)}{g_0(v,i+1)+f_1(u,i)*f_0(v,i-1)+f_1(v,i-1)*f_0(u,i)}(i>0))
    两个点都在v的子树内时,(点对点权和)之和为(g_0(v,i+1));选的第一个点在其他儿子的子树中,另一个在v的子树中时,“可以作为第一个点的点”的点权和为(f_1(u,i)),与之对应的点有(f_0(v,i-1))种取法,因此选的第一个点的贡献总和是(f_1(u,i)*f_0(v,i-1)),另一个点同理,贡献为(f_1(v,i-1)*f_0(u,i))
    (spacef g_0(u,0)=sum_{vin son(u)}{g_0(v,1)})
    这些点对((x,y))都满足(dis(u,x)=dis(u,y)=dis(x,y))(dis(u,LCA(x,y))=dis(x,LCA(x,y))=dis(y,LCA(x,y))),又因为(x eq y),所以(dis(u,LCA(x,y)) eq0),也就是说点对((x,y))中的两点一定在(u)的同一个儿子的子树,那就直接从儿子的(g_0)转移就行了
    (spacef g_1(u,i)=sum_{vin son(u)}{g_1(v,i+1)+f_1(u,i)*f_1(v,i-1)}(i>0))
    两个点都在v的子树内时,同(g_0);选的第一个点在其他儿子的子树中,另一个在v的子树中时,“可以作为第一个点的点”的点权和为(f_1(u,i)),“可以作为另一个点的点”的点权和为(f_1(v,i-1)),将它们相乘就行了
    (spacef g_1(u,0)=sum_{vin son(u)}{g_1(v,1)})
    (g_0)
    (spacef ans(u)=sum_{k}sum_{vin son(u)}{g_1(v,k+1)*f_0(u,k)+g_0(v,k+1)*f_1(v,k)+g_1(u,k+1)*f_0(v,k)+g_0(u,k+1)*f_1(v,k)})
    两个点((x,y))(v)的子树中,一个点(z)在外面时,(x,y)所有取法的点权积之和是(g_1(v,k+1)),可以与(f_0(u,k))(z)相匹配,所以所有的(x*y)之和为(g_1(v,k+1)*f_0(u,k))(x,y)所有取法的点权和之和是(g_0(v,k+1)),可以与之相乘的(z)的和为(f_1(u,k)),所以所有的(x*z+y*z)之和为(g_0(v,k+1)*f_1(v,k));两个点((x,y))在外面,一个点((z))(v)的子树中时,同理,所有的(x*y)之和为(g_1(u,k+1)*f_0(v,k)),所有的(x*z+y*z)之和为(g_0(u,k+1)*f_1(v,k))
    ----------------暴力与不暴力的分割线-------------
    会发现合并每个点(u)和它的第一个儿子时,相当于把这个儿子的(f,g)赋给(u),只不过(f)向后错了一位,(g)向前错了一位
    合并(u)与后面的儿子时,设(dep(x))为“点(x)的子树中到点(x)最远的点”与点(x)的距离,那么合并(u)与它的儿子(v)的时间复杂度为(Theta(dep(v)))),求(u)的儿子(v)(ans(u))的贡献的复杂度也是这个
    那么就可以考虑对这棵树进行长链剖分,每次将(u)的长儿子的(f,g)(u)(是“直接给”,而不是“用一个循环赋值”!这样这一步的时间复杂度就是(Theta(1))了),再将(u)与它的每个短儿子合并(这一步的时间复杂度是(Theta(sum_{vin son(u)}{dep(v)}))),总时间复杂度可以看成只有所有长链的链顶会对总时间复杂度有贡献,(总时间复杂度=Theta(sum{链长})=Theta(n))
    其中关键的一步,“将(u)的长儿子的(f,g)(u)”可以用指针实现,不过并不对劲的人并不会使用指针,那就可以将(f(u,i))放到第(dfn[u]+i)(dfn[u])表示先走长儿子的dfs序)的位置,将(g(u,i))放到(dfn[top[u]]+dep(u)+i)(dep(x))还是表示“点(x)的子树中到点(x)最远的点”与点(x)的距离)的位置,只不过略有麻烦

    代码

    “略有”麻烦?
    不使用指针的做法,关键在于要设计出两个映射(F(x,i),G(x,i)),使(F(i,k)=F(长儿子(i),k-1),G(i,k)=G(长儿子(i),k+1))

    #include<algorithm>
    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<ctime>
    #include<iomanip>
    #include<iostream>
    #include<map>
    #include<queue>
    #include<set>
    #include<stack>
    #include<vector>
    #define rep(i,x,y) for(register int i=(x);i<=(y);++i)
    #define dwn(i,x,y) for(register int i=(x);i>=(y);--i)
    #define view(u,k) for(int k=fir[u];k!=-1;k=nxt[k])
    #define LL long long
    #define maxn 2000010 
    #define maxm (maxn<<1)
    #define F(x,i) (dfn[x]+i)
    #define G(x,i) ((dfn[top[x]]<<1)+dep[x]+i)
    using namespace std;
    int read()
    {
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)&&ch!='-')ch=getchar();
    	if(ch=='-')f=-1,ch=getchar();
    	while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    	return x*f;
    }
    void write(int x)
    {
    	if(x==0){putchar('0'),putchar('
    ');return;}
    	int f=0;char ch[20];
    	if(x<0)putchar('-'),x=-x;
    	while(x)ch[++f]=x%10+'0',x/=10;
    	while(f)putchar(ch[f--]);
    	putchar('
    ');
    	return;
    }
    const LL mod=998244353;
    int n,fir[maxn],nxt[maxm],v[maxm],dep[maxn],dfn[maxn],tim,son[maxn],fa[maxn],top[maxn],cnt;
    int f[2][maxn],g[2][maxm],w[maxn],ans,col[maxn],num[maxn];
    void ade(int u1,int v1){v[cnt]=v1,nxt[cnt]=fir[u1],fir[u1]=cnt++;}
    void getdep(int u)
    {
    	view(u,k)if(v[k]!=fa[u])
    	{
    		fa[v[k]]=u,getdep(v[k]),dep[u]=max(dep[u],dep[v[k]]+1);
    		if(!son[u]||dep[v[k]]>dep[son[u]])son[u]=v[k];
    	}
    }
    void getdfn(int u,int anc)
    {
    	top[u]=anc,dfn[u]=++tim;
    	if(son[u])getdfn(son[u],anc);
    	view(u,k)if(v[k]!=son[u]&&v[k]!=fa[u])getdfn(v[k],v[k]);
    }
    void getans(int u)
    {
    	if(son[u])getans(son[u]);
    	view(u,k)if(v[k]!=fa[u]&&v[k]!=son[u])
    	{
    		getans(v[k]);
    		rep(i,0,dep[v[k]])
    		{
    			ans=((LL)ans+(LL)g[1][G(v[k],i+1)]*(LL)f[0][F(u,i)]%mod+(LL)g[0][G(v[k],i+1)]*(LL)f[1][F(u,i)]%mod)%mod,
    			ans=((LL)ans+(LL)g[1][G(u,i+1)]*(LL)f[0][F(v[k],i)]%mod+(LL)g[0][G(u,i+1)]*(LL)f[1][F(v[k],i)]%mod)%mod;
    		}
    		g[0][G(u,0)]=(g[0][G(u,0)]+g[0][G(v[k],1)])%mod,g[1][G(u,0)]=(g[1][G(u,0)]+g[1][G(v[k],1)])%mod; 
    		rep(i,1,dep[v[k]]+1)
    		{
    			g[0][G(u,i)]=((LL)g[0][G(u,i)]+(LL)g[0][G(v[k],i+1)]+(LL)f[1][F(u,i)]*(LL)f[0][F(v[k],i-1)]%mod+(LL)f[1][F(v[k],i-1)]*(LL)f[0][F(u,i)]%mod)%mod,
    			g[1][G(u,i)]=((LL)g[1][G(u,i)]+(LL)g[1][G(v[k],i+1)]+(LL)f[1][F(u,i)]*(LL)f[1][F(v[k],i-1)]%mod)%mod;
    			f[0][F(u,i)]=(f[0][F(u,i)]+f[0][F(v[k],i-1)])%mod,
    			f[1][F(u,i)]=(f[1][F(u,i)]+f[1][F(v[k],i-1)])%mod;
    		}
    	}
    	f[0][F(u,0)]=1,f[1][F(u,0)]=w[u],
    	ans=((LL)ans+(LL)g[1][G(u,0)]+(LL)g[0][G(u,0)]*(LL)w[u]%mod)%mod;
    	g[1][G(u,0)]=g[0][G(u,0)]=0;
    }
    int main()
    {
    	freopen("tree.in","r",stdin);
    	freopen("tree.out","w",stdout);
    	memset(fir,-1,sizeof(fir));
    	n=read();
    	rep(i,1,n-1){int x=read(),y=read();ade(x,y),ade(y,x);}
    	rep(i,1,n)w[i]=read();
    	getdep(1),getdfn(1,1),getans(1);
    	write(ans);
    	return 0;
    }
    /*
    10
    2 1
    3 2
    4 3
    5 3
    6 3
    7 6
    8 7
    9 5
    10 3
    6 6 9 8 9 5 3 4 10 2
    */
    //1143
    
  • 相关阅读:
    JSP显示新闻
    servlet应用
    J2EE_第二次作业_登陆页面
    J2EE第一次作业
    软工最后一次作业
    第三次作业(赵清和李靖)
    第二次作业
    第一次作业
    分布式系统架构之构建你的任务调度中心
    【原创】新零售の从单体系统向微服务演变历程(一)
  • 原文地址:https://www.cnblogs.com/xzyf/p/10474515.html
Copyright © 2011-2022 走看看