zoukankan      html  css  js  c++  java
  • 【BZOJ5211】[ZJOI2018]线图(树哈希,动态规划)

    【BZOJ5211】[ZJOI2018]线图(树哈希,动态规划)

    题面

    BZOJ
    洛谷

    题解

    吉老师的题目是真的神仙啊。
    去年去现场这题似乎骗了(20)分就滚粗了?
    首先(k=2)直接算(k=1)时的边数就好了。(k=3)同理。
    这里直接计算每个点的度数就可以做,然后就有(20)分了。
    我们发现如果企图继续考虑线图应该怎么计算出来,这里是很难做的。
    注意到原图是一棵树,所以想想线图和原图之间的关系。
    对于做一次线图(L(G))而言,点数显然等于原图的边数。
    对于做两次线图(L^2(G))而言,点数就是在(L(G))中的边数。(L(G))中的点在原图中表示边,如果两个点在(L(G))中有边,证明在原图中他们两是有交点的边,即一条长度为(2)的路径。
    对于做三次线图(L^3(G))而言,即(L^2(G))的边数。而(L^2(G))的边表示他们在(L(G))上是一条长度为(2)的边,那么放在原图上,发现可以是一个长度为(3)的链,亦或者是三条共点的边。
    那么如果继续口胡呢?(L^4(G))是啥呢?那么就是(L^2(L^2(G)))。考虑在(L^2(G))上找长度为(2)的路径。显然长度为(4)的链是可以的,然后四条共点的边也是可以的。其实回到(L^2(2))的边的含义,那么不难发现(L^4(G))也就是原图中两条长度为(2)的路径拼起来,如果要拼起来显然要有一个交点,所以实际上就是一棵拥有(4)条连在一起的东西,放在一棵树的原图中,就是一个点数为(5)的树。
    那么这么分析一下,似乎发现前面的(L(G))对应着边数为(1)、点数为(2)的树。(L^2(G))对应着边数为(2),点数为(3)的树。(L^3(G))对应着边数为(3)、点数为(4)的树......
    真的?就这么简单?
    然而仔细想想这样子似乎有点锅。因为我们发现在线图的构建过程中是会有环的出现,最简单的例子就是如果(G)并不是题目给出的树的话,那么显然原图中的三元环也要计入答案。
    所以来改正一下我们的措辞,(L^k(G))的每一个点对应着原图(G)中的一个边数不超过(k)的联通的导出子图的个数。注意这里的用词,是一个点对应着一个导出子图,而不是每一个导出子图对应着一个点,同时也意味着一个导出子图可能被多个点所对应。
    而原图就是一棵树,所以实际上的联通导出子图只可能是一棵树的形态。
    对于每个边数不超过(k)的子树的贡献太慢了,显然是把每种子树都统计一下个数,然后再对于每种子树计算贡献然后统计答案。
    贡献不好算?直接暴力算(k)次不就完事了?然而这样子就会发现复杂度单次计算的复杂度很爆炸。
    我们发现(k)很小的时候可以直接推式子来快速计算,所以我们只需要模拟出一个较小的(k)的图然后直接计算。
    那么我们来推推式子?假设(n)是点数,(m)是边数。
    对于(L(G)),显然(ans=m)
    对于(L^2(G))(ans=sum_{i=1}^n {deg(i)choose 2})
    对于(L^3(G)),考虑长度为三的链以及三元环的贡献(sum _{(u,v)in E}(deg(u)-1)*(deg(v)-1)),即枚举中间那条边,考虑以这条边的两个端点再延伸出去。然后再考虑一个点挂三条边的贡献,就是(3sum_{i=1}^n {deg(i)choose 3}),乘三的原因是这样的三条边在求线图后成环,贡献了三个点。两个式子求和就是(L^3(G))的贡献了。
    然后(L^4(G))怎么算?直接算肯定不方便,所以变形一下成了(L^3(L(G)))。而(L(G))每个点的度数是很好算的,为(deg((u,v))=deg(u)+deg(v)-2,(u,v)in E)
    而计算(L^3(G))时对于每条边计算贡献时,我们不可能把每条边全部整出来算。这样子考虑,(L(G))中的一个点表示的是(G)中的一条边,而(L(G))中的一条边,表示的是原图中的一个长度为(2)的链,现在要顺次考虑每一条边的贡献,等价于在原图中考虑每一个长度为(2)的链的贡献。而一个长度为(2)的链由三个点构成,所以我们枚举其中中间的那个点,那么这两条边都从这个点射出。
    所以令(S(u))表示以(u)为端点的边((u,v)in E)的所有边的(deg((u,v))-1)的和,那么贡献就是(frac{1}{2}S(u)^2),然而这个东西算重了,每条边在每个端点时都被自己和自己匹配了(frac{1}{2})次,也就是一共被多算了(1)次,所以还需要减去(sum_{(u,v)in E}(deg((u,v))-1)^2)
    所以(L^4(G))的答案就是(frac{1}{2}sum_{i=1}^n S(i)^2-sum_{i=1}^m (deg(i)-1)^2+3sum_{i=1}^m{deg(i)choose 3})
    再往后似乎也能继续推,但是也没有什么必要了。

    好的,那么现在我们要做的就是两个事情,统计每种边数不超过(k)的树的贡献,以及它在原树中的出现次数。
    我们先考虑怎么求解边数不超过(k)的树的贡献,可以类似括号序列的方法爆搜树,左括号表示当前点加入一个儿子,右括号表示回到父亲,但是这样会计算出同构的树,所以再树哈希一下去重。
    那么我们计算一棵树(T)(L^k(T))的点数,就是前文所说的,只需要模拟出(L^{k-4}(T)),然后直接计算即可。
    接着是考虑如何计算贡献,显然就是对于当前联通块而言减去其所有联通导出子图的贡献,这个容斥处理即可。
    然后就是对于每棵搜出来的树,如何计算它在原树上的出现次数。
    我们来做一个(dp),设(f[i][r])表示以原树上的(i)节点为根节点,当前点可以匹配上目标树上的(r)的子树的方案数。
    转移的时候枚举当前点匹配目标树上的哪个节点,那么它的儿子就要和目标树上枚举的这个节点对应,这个直接(dp)就好了。注意一个小问题,因为子树是可以同构的,所以这里要考虑可重排列的贡献。
    当然,如果直接暴力(dp)复杂度是比较难接受的,这里做一个小小的优化,把目标树上的叶子节点全部删掉,在匹配完之后再考虑当前点挂的叶子节点,这样子可以直接用一个组合数计算方案数,同时可以把这里要求解的点数优化很多。

    本机(2.2s),洛谷和(LOJ)都能过,(UOJ)我卡不动了,(BZOJ)今天挂了,所以就这样了。
    upd:BZOJ上莫名CE。。。。

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    using namespace std;
    #define MOD 998244353
    #define inv2 499122177
    #define ull unsigned int
    #define MAX 5050
    void halt(){exit(0);}
    inline int read()
    {
        int x=0;bool t=false;char ch=getchar();
        while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
        if(ch=='-')t=true,ch=getchar();
        while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
        return t?-x:x;
    }
    int fpow(int a,int b)
    {
    	int s=1;
    	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    	return s;
    }
    int n,k,ans;
    int S[MAX*2000],dg[MAX*2000];
    int C[MAX][15],JC[15];
    const ull base1=998,base2=244,base3=353;
    struct Graph
    {
    	struct Line{int v,next;}e[MAX*2000];
    	int h[MAX*2000],cnt,n;
    	void pre(int N){n=N;cnt=2;for(int i=1;i<=n;++i)h[i]=0;}
    	inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
    	int Calc4()
    	{
    		int ans=0;for(int i=1;i<=n;++i)dg[i]=S[i]=0;
    		for(int u=1;u<=n;++u)
    			for(int i=h[u];i;i=e[i].next)dg[e[i].v]+=1;
    		for(int u=1;u<=n;++u)
    			for(int i=h[u];i;i=e[i].next)
    				S[u]+=dg[u]+dg[e[i].v]-3;
    		for(int i=1;i<=n;++i)ans=(ans+1ll*S[i]*S[i])%MOD;
    		ans=1ll*ans*inv2%MOD;
    		for(int i=2;i<cnt;i+=2)
    		{
    			int d=dg[e[i].v]+dg[e[i^1].v]-2;
    			ans=(ans+MOD-(d-1)*(d-1))%MOD;
    			ans=(ans+1ll*d*(d-1)*(d-2)/2)%MOD;
    		}
    		return ans;
    	}
    }T,E[2];
    int lg[MAX],bul[MAX];
    int lb(int x){return x&(-x);}
    struct Tree
    {
    	ull f[15],Q[15];int n,E[15],size[15],jc[15];
    	void Add(int u,int v){E[u]|=1<<v;E[v]|=1<<u;}
    	void Resize(int u,int ff)
    	{
    		size[u]=1;
    		for(int i=E[u];i;i-=lb(i))
    			if(lg[lb(i)]!=ff)
    				Resize(lg[lb(i)],u),size[u]+=size[lg[lb(i)]];
    	}
    	void dfs(int u,int ff)
    	{
    		size[u]=1;
    		for(int i=E[u];i;i-=lb(i))
    			if(lg[lb(i)]!=ff)
    				dfs(lg[lb(i)],u),size[u]+=size[lg[lb(i)]];
    		int son=0;for(int i=E[u];i;i-=lb(i))Q[++son]=f[lg[lb(i)]];
    		sort(&Q[1],&Q[son+1]);f[u]=size[u];jc[u]=1;Q[son+1]=Q[son]+1;
    		for(int i=1;i<=son;++i)f[u]=f[u]*base1+Q[i];Q[0]=Q[1]+1;
    		for(int i=1,s=1,cnt=1;i<=son+1;++i)
    			if(Q[i]==Q[i-1])++cnt,s=1ll*s*cnt%MOD;
    			else jc[u]=1ll*jc[u]*s%MOD,cnt=s=1;
    		jc[u]=fpow(jc[u],MOD-2);
    		f[u]*=size[u]*base2+base3;
    	}
    	int sz[15];ull F[15];
    	void CalcHash(int u,int ff,int S,int &T)
    	{
    		sz[u]=1;T|=1<<u;
    		for(int i=E[u]&S;i;i-=lb(i))
    			if(lg[lb(i)]!=ff)
    				CalcHash(lg[lb(i)],u,S,T),sz[u]+=sz[lg[lb(i)]];
    		int son=0;for(int i=E[u]&S;i;i-=lb(i))Q[++son]=F[lg[lb(i)]];
    		sort(&Q[1],&Q[son+1]);F[u]=sz[u];
    		for(int i=1;i<=son;++i)F[u]=F[u]*base1+Q[i];
    		F[u]*=sz[u]*base2+base3;
    	}
    	void Hash(){dfs(0,0);}
    	int check(int S)
    	{
    		int mx=lg[lb(S)];
    		for(int i=0;i<n;++i)if(S&(1<<i))if(size[mx]<size[i])mx=i;
    		int vis=0;CalcHash(mx,0,S,vis);return vis==S?mx:-1;
    	}
    	void clear(){for(int i=0;i<15;++i)E[i]=size[i]=sz[i]=F[i]=f[i]=jc[i]=Q[i]=0;}
    };
    void SpecialCheck()
    {
    	int ans=0;
    	if(k==1)printf("%d
    ",n-1),halt();
    	if(k==2)
    	{
    		for(int i=1;i<=n;++i)ans=(ans+1ll*dg[i]*(dg[i]-1)/2)%MOD;
    		printf("%d
    ",ans);halt();
    	}
    	if(k==3)
    	{
    		for(int i=2;i<T.cnt;i+=2)
    		{
    			int d=dg[T.e[i].v]+dg[T.e[i^1].v]-2;
    			ans=(ans+1ll*d*(d-1)/2)%MOD;
    		}
    		printf("%d
    ",ans);halt();
    	}
    	if(k==4)printf("%d
    ",T.Calc4()),halt();
    }
    void GetLineGraph(Graph &a,Graph &b)
    {
    	int N=0;
    	for(int u=1;u<=a.n;++u)
    		for(int i=a.h[u];i;i=a.e[i].next)N+=1;
    	N/=2;b.pre(N);
    	for(int u=1;u<=a.n;++u)
    		for(int i=a.h[u];i;i=a.e[i].next)
    			for(int j=a.e[i].next;j;j=a.e[j].next)
    				b.Add(i>>1,j>>1),b.Add(j>>1,i>>1);
    }
    map<ull,int> M;
    int CalcNode(int k)
    {
    	int nw=0,pw=1;k-=4;
    	while(k--)GetLineGraph(E[nw],E[pw]),nw^=1,pw^=1;
    	return E[nw].Calc4();
    }
    int CalcValue(Tree &a,int k)
    {
    	a.Hash();E[0].pre(a.n);
    	for(int i=0;i<a.n;++i)
    		for(int j=a.E[i];j;j-=lb(j))
    			E[0].Add(i+1,lg[lb(j)]+1),E[0].Add(lg[lb(j)]+1,i+1);
    	int ret=CalcNode(k);
    	for(int i=1;i<(1<<a.n)-1;++i){int p=a.check(i);if(~p)if(M.find(a.F[p])!=M.end())ret=(ret+MOD-M[a.F[p]])%MOD;}
    	return M[a.f[0]]=ret;
    }
    int leaf[15],f[MAX][15];
    bool Leaf[15];
    Tree now;int pre[MAX];
    void dfs(int u,int ff)
    {
    	int son=0;
    	for(int i=T.h[u];i;i=T.e[i].next)
    		if(T.e[i].v!=ff)++son,dfs(T.e[i].v,u);
    	for(int i=0;i<now.n;++i)
    	{
    		if(Leaf[i])continue;
    		if(son<bul[now.E[i]]){f[u][i]=0;continue;}
    		for(int j=now.E[i];j;j=(j-1)&now.E[i])pre[j]=0;
    		pre[0]=1;
    		for(int j=T.h[u];j;j=T.e[j].next)
    		{
    			if(T.e[j].v==ff)continue;
    			for(int k=now.E[i];k;k=(k-1)&now.E[i])
    				for(int p=k;p;p-=lb(p))
    					pre[k]=(pre[k]+1ll*pre[k^lb(p)]*f[T.e[j].v][lg[lb(p)]])%MOD;
    		}
    		f[u][i]=1ll*pre[now.E[i]]*now.jc[i]%MOD*C[son-bul[now.E[i]]][leaf[i]]%MOD*JC[leaf[i]]%MOD;
    	}
    }
    int CalcTimes(Tree &a)
    {
    	now=a;
    	for(int i=0;i<a.n;++i)leaf[i]=0,Leaf[i]=false;
    	for(int i=0;i<a.n;++i)
    		for(int j=a.E[i];j;j-=lb(j))
    			if(a.size[lg[lb(j)]]==1)
    				++leaf[i],now.E[i]^=lb(j);
    	for(int i=0;i<a.n;++i)if(a.size[i]==1)Leaf[i]=true;
    	now.Resize(0,0);dfs(1,0);int ret=0;
    	for(int i=1;i<=n;++i)ret=(ret+f[i][0])%MOD;
    	return ret;
    }
    int St[MAX<<1],fa[MAX];
    set<ull> Vis;
    Tree p;
    void dfsTrees(int x,int c,int sp,int lim)
    {
    	if(x==(lim-1)*2+1)
    	{
    		int now=0,tot=0;p.clear();
    		for(int i=1;i<x;++i)
    			if(St[i]==1)p.E[now]|=1<<(++tot),fa[tot]=now,now=tot;
    			else now=fa[now];
    		p.n=lim;p.Hash();
    		if(Vis.find(p.f[0])!=Vis.end())return;Vis.insert(p.f[0]);
    		ans=(ans+1ll*CalcValue(p,k)*CalcTimes(p))%MOD;
    		return;
    	}
    	if(c)St[x]=-1,dfsTrees(x+1,c-1,sp,lim);
    	if(sp<lim-1)St[x]=1,dfsTrees(x+1,c+1,sp+1,lim);
    }
    int main()
    {
    	n=read();k=read();T.pre(n);
    	for(int i=1;i<n;++i)
    	{
    		int u=read(),v=read();dg[u]+=1,dg[v]+=1;
    		T.Add(u,v),T.Add(v,u);
    	}
    	SpecialCheck();
    	for(int i=2;i<MAX;++i)lg[i]=lg[i>>1]+1;
    	for(int i=1;i<MAX;++i)bul[i]=bul[i^lb(i)]+1;
    	for(int i=0;i<MAX;++i)C[i][0]=1;JC[0]=1;
    	for(int i=1;i<15;++i)JC[i]=1ll*JC[i-1]*i%MOD;
    	for(int i=1;i<MAX;++i)
    		for(int j=1;j<=i&&j<15;++j)
    			C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
    	for(int i=1;i<=k+1;++i)dfsTrees(1,0,0,i),Vis.clear();
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    DebugView使用技巧
    网络抓包--Wireshark
    常用curl命令
    chrome.debugger
    修改php.ini 的timezone
    初识Elasticsearch,bulk 操作的遇到的那些事
    chrome 扩展 调试
    sqlite 时间戳转时间
    centos 升级sqlite3
    php 安装redis
  • 原文地址:https://www.cnblogs.com/cjyyb/p/10348386.html
Copyright © 2011-2022 走看看