LOJ2433. 「ZJOI2018」線圖

題目


正解

參考:
官方題解:https://blog.csdn.net/qq_16267919/article/details/79675232
https://www.luogu.com.cn/blog/ShadowassIIXVIIIIV/solution-p4337
(極度推薦這篇博客,講解得非常詳細)

由於上面的那篇博客講得比較清楚,所以我這裏就簡單地概括一下:
首先考慮Lk(G)L_k(G)中的每個點代表什麼:
L1L_1中一個點代表GG中一條邊。
L2L_2中一個點代表GG中相連的兩條邊。
L3L_3中一個點代表GG中相連的三條邊(長度爲33的鏈(包括三元環)、或一個點連出的三條邊)。
於是總結出Lk(G)L_k(G)中的一個點表示GG中的kk條邊組成的連通塊。
這樣表述有些問題,修正一下:Lk(G)L_k(G)中的一個點表示GG中的不超過kk條邊組成的連通塊。
並且相同的連通塊可能被多個節點表示。
然後又可以發現,對於GG中的一個連通塊SS,求Lk(S)L_k(S),它恰好是Lk(G)L_k(G)的一個連通塊。
想要比較好的理解這些性質,建議拿幾個樣例來手玩一下。上面推薦的那篇博客舉的樣例不錯,手玩一下就能夠比較好理解。

由於題目中的GG是一棵樹,所以這也可以轉化成不超過k+1k+1個點組成的連通塊。
枚舉一個不超過k+1k+1個點的連通塊TT,計算Lk(T)L_k(T)中有多少個表示TT的點,記爲wTw_T。然後在GG中找不同的TT的個數,記爲tTt_T。最後wTtT\sum w_Tt_T就是答案。
爲了方便這裏TT指有根樹。

計算wTw_T
考慮將Lk(T)L_k(T)的點數算出來,作爲wTw_T。這時候發現會算多,因爲這把TT的聯通子圖的貢獻都算了進去。
於是枚舉TT的聯通子圖SS,計算wS\sum w_S,減去即可。
枚舉聯通子圖的時間相比於下面是比較少的,忽略不計。
如果k4k\leq 4,可以通過人類智慧將Lk(G)L_k(G)的點數求出來(此時不需要保證GG是棵樹):
k=1k=1時,答案爲邊數。
k=2k=2時,答案爲GG中有多少對相鄰的邊,於是答案爲C(degi,2)\sum C(deg_i,2)
k=3k=3時,答案爲GG中有多少條長度爲33的鏈和一個點連出去三條邊的方案數(注意這個每個方案貢獻爲33)。
k=4k=4時,考慮L4(G)=L3(L1(G))L_4(G)=L_3(L_1(G)),通過將L1(G)L_1(G)中每個點(對應GG中一條邊)的度數算出來,套進k=3k=3的式子中,化一下式子就出來了。
時間複雜度都是O(+)O(點數+邊數)
具體式子上面推薦的博客有。
kk更大咋辦?暴力算出Lk4(G)L_{k-4}(G),然後套用上面的方法算出L4(Lk4(G))L_4(L_{k-4}(G))
考慮迭代一次點數大概乘kk,所以時間複雜度大概爲O(kk4)O(k^{k-4}),點數大概開到1e51e5,邊數我開到了1e71e7(可能可以少些吧)。
於是這一部分爲O(1205kk4)O(1205*k^{k-4}),其中12051205爲大小小於等於1010的本質不同的有根樹個數。
有個大優化:做無根樹哈希,如果當前的答案之前算過就不用計算。

計算tTt_T
fi,jf_{i,j}表示將有根樹jj的根放到ii節點上,多少種方案。
轉移的時候枚舉有根樹jj的根的每個兒子所代表的子樹,和ii的兒子匹配。套一個狀壓DP實現。
這樣時間複雜度是O(1205n2k)O(1205*n*2^k)
似乎有點慢,加個小優化:不考慮有根樹jj的根的每個兒子直接是葉子節點的情況。狀壓DP之後再將葉子結點用個組合數計算貢獻。
注意在算的過程中可能會有重複計算的情況,於是對於有根樹jj的根的兒子中,對於每種不同的子樹計算相同的個數,答案除以它們的階乘。(其實也可以在狀壓的時候不用二進制壓表示每個子樹選或不選,而是壓每種子樹用了多少個。這樣理論上還快些。)
時間複雜度O(1205n2k2)O(1205*n*2^\frac{k}{2})


代碼

8k,我醉了……
講一下實現細節(不一定和程序中一樣):
處理不同的有根樹,而且還要處理出根的兒子子樹的編號。
我程序中的方法從有根樹點數小到大枚舉,枚舉括號序。枚舉之後判斷兒子子樹的編號是否有序,如果不有序就不算。
應該還有一種比較優美的方式:按點數從小到大枚舉。枚舉直接與根相連的子樹的種類,枚舉的過程保證編號不上升。然後給枚舉出來的子樹標號。

無根樹哈希大概就是找重心。如果重心有兩個就在邊中間插一個點。
以其爲根求括號序。
由於連出的兒子本是無序的,所以先給連出的兒子的哈希值排序之後再計算。

枚舉一棵樹的聯通子圖的時候,先算不包括根的聯通子圖。設swTsw_T表示TT中所有聯通子圖的ww之和,之前已經處理了,直接算。
然後算包括根的聯通子圖。先求dfsdfs序,對於一個點,如果它選,下一個考慮就是它的第一個兒子,否則下一個考慮它子樹之外。

最後提醒:模塊化!模塊化!
(話說那些3k或4k的怎麼做到的?)

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <cassert>
#define ll long long
const int N=5010;
const int mo=998244353;
const int inv2=499122177;
int n,k;

//Section:fac,ifac,C(m,n)
ll fac[N],ifac[N];
void initC(int n){
	ifac[1]=1;
	for (int i=2;i<=n;++i)
		ifac[i]=(mo-mo/i)*ifac[mo%i]%mo;
	fac[0]=ifac[0]=1;
	for (int i=1;i<=n;++i){
		fac[i]=fac[i-1]*i%mo;
		ifac[i]=ifac[i-1]*ifac[i]%mo;
	}
}
ll C(int m,int n){return fac[m]*ifac[n]%mo*ifac[m-n]%mo;}

//Section:Graph
struct EDGE{int to;EDGE *las;};
template <int _N,int _M>
struct Graph{
	int n=_N;
	EDGE e[_M];
	int ne;
	EDGE *last[_N+1];
	void init(int _n=0){n=_n,ne=0,memset(last,0,sizeof(EDGE*)*(n+1));}
	void link(int u,int v){e[ne]={v,last[u]};last[u]=e+ne++;}
};
Graph<N,N*2> G;

//Section:Get Rooted Tree
map<int,int> id;
int cnt;
int siz[1300];
vector<int> son[1300];
int lf[1300],same[1300];
//lf:Num of leaves connecting to rt directly
//same:Pro of 1/(Num of same subT connecting to rt directly)!
int rt_hash(int n,int s){return s*22+2*n-1;}
void grt(int x,int k,int sum,int s){
	if (sum<0)
		return;
	if (x==2*k-2){
		if (sum)
			return;
		s=(s<<1)+(1<<2*k-1);
		++cnt;
		int p=0,_lf=0,_same=1,lst=0,c=0;
		for (int i=1,j=1;i<2*k-1;++i){
			p+=(s>>i&1?-1:1);
			if (p==0){
				int a=rt_hash(i-j+1>>1,(s&(1<<i+1)-1)>>j);
				if (id.find(a)==id.end()){son[cnt--].clear();return;}
				a=id[a];
				son[cnt].push_back(a);
				_lf+=(a==1);
				if (a==lst)
					c++;
				else{
					_same=(ll)_same*ifac[c]%mo;
					lst=a,c=1;
				}
				j=i+1;
			}
		}
		_same=(ll)_same*ifac[c]%mo;
		for (int i=1;i<son[cnt].size();++i)
			if (son[cnt][i-1]>son[cnt][i]){son[cnt--].clear();return;}
		int key=rt_hash(k,s);
		id[key]=cnt;
		siz[cnt]=k,lf[cnt]=_lf,same[cnt]=_same;
		return;
	}
	grt(x+1,k,sum+1,s);
	grt(x+1,k,sum-1,s+(1<<x));
}

//Sectioon: Hash:Unrooted Tree
template <int _N,int _M>
void build_ut(int x,int t,int &n,Graph<_N,_M> &G){//build UT by id of RT
	if (siz[t]==1)
		return;
	for (int i=0;i<son[t].size();++i){
		++n,G.link(x,n),G.link(n,x);
		build_ut(n,son[t][i],n,G);
	}
}
int G0,G1,all;
template <int _N,int _M>
void findG(int x,int fa,Graph<_N,_M> &G,int siz[]){//find center of gravity 
	siz[x]=1;
	bool is=1;
	for (EDGE *ei=G.last[x];ei;ei=ei->las)
		if (ei->to!=fa){
			findG(ei->to,x,G,siz);
			siz[x]+=siz[ei->to];
			is&=(siz[ei->to]<=all>>1);
		}
	is&=(all-siz[x]<=all>>1);
	if (is) (G0?G1:G0)=x;
}
int *_siz,*_key;
bool cmpp(int a,int b){return _siz[a]<_siz[b] || _siz[a]==_siz[b] && _key[a]<_key[b];}
template <int _N,int _M>
void gethash(int x,int fa,Graph<_N,_M> &G,int siz[],int key[]){
	siz[x]=1;
	for (EDGE *ei=G.last[x];ei;ei=ei->las)
		if (ei->to!=fa)
			gethash(ei->to,x,G,siz,key),siz[x]+=siz[ei->to];
	static int p[12];
	int cnt=0;
	for (EDGE *ei=G.last[x];ei;ei=ei->las)
		if (ei->to!=fa)
			p[cnt++]=ei->to;
	_siz=siz,_key=key;
	sort(p,p+cnt,cmpp);
	key[x]=0;
	for (int i=cnt-1,s=0;i>=0;--i){
		key[x]+=key[p[i]]<<s;
		s+=siz[p[i]]*2;
	}
	key[x]=(key[x]<<1)+1;
}
template<int _N,int _M>
int ut_hash(Graph<_N,_M> &G,int n,bool rem=1){//rem:whether G can be changed
	static int sz[12],key[12];
	G0=G1=0,all=n,findG(1,0,G,sz);
	int rt=G0;
	if (G1){
		++n,G.last[n]=NULL;
		G.link(n,G0),G.link(n,G1);
		for (EDGE *ei=G.last[G0];ei;ei=ei->las)
			if (ei->to==G1){ei->to=n;break;}
		for (EDGE *ei=G.last[G1];ei;ei=ei->las)
			if (ei->to==G0){ei->to=n;break;}
		rt=n;
	}
	gethash(rt,0,G,sz,key);
	ll res=rt_hash(key[rt],n)*(G1?-1:1);
	if (!rem && G1){
		G.ne-=2;
		for (EDGE *ei=G.last[G0];ei;ei=ei->las)
			if (ei->to==n){ei->to=G1;break;}
		for (EDGE *ei=G.last[G1];ei;ei=ei->las)
			if (ei->to==n){ei->to=G0;break;}
		G.last[n--]=NULL;
	}
	return res;
}
int rt_to_ut(int t){
	static Graph<11,11*2> G;
	G.init(siz[t]+1);
	int n;
	build_ut(1,t,n=1,G);
	return ut_hash(G,siz[t]);
}
//Section: Calc w
int w[N],sw[N];
map<int,int> ut_w,ut_sw;
Graph<100010,10000010> L[2];
Graph<11,11*2> T,S;
template <int _N,int _M>
int calc234(Graph<_N,_M> &G,int k){
	static int deg[100010];
	if (k==0)
		return G.n;
	if (k==1)
		return G.ne/2;
	memset(deg,0,sizeof(int)*(G.n+1));
	for (int i=1;i<=G.n;++i)
		for (EDGE *ei=G.last[i];ei;ei=ei->las)
			if (i<ei->to)
				deg[i]++,deg[ei->to]++;
	ll r=0;
	if (k==2){
		for (int i=1;i<=G.n;++i)
			(r+=(ll)deg[i]*(deg[i]-1)%mo*inv2)%=mo;
	}
	if (k==3){
		for (int i=1;i<=G.n;++i)
			for (EDGE *ei=G.last[i];ei;ei=ei->las)
				if (i<ei->to)
					(r+=(ll)(deg[i]-1)*(deg[ei->to]-1))%=mo;
		for (int i=1;i<=G.n;++i)
			(r+=(ll)deg[i]*(deg[i]-1)%mo*(deg[i]-2)%mo*inv2)%=mo;
	}
	if (k==4){
		for (int i=1;i<=G.n;++i){
			ll d2=0;
			for (EDGE *ei=G.last[i];ei;ei=ei->las){
				ll d1=deg[i]+deg[ei->to]-2;
				d2+=d1-1;
			}
			(r+=d2*d2)%=mo;
		}
		r=r*inv2%mo;
		for (int i=1;i<=G.n;++i)
			for (EDGE *ei=G.last[i];ei;ei=ei->las)
				if (i<ei->to){
					ll d1=deg[i]+deg[ei->to]-2;
					r=((r-(d1-1)*(d1-1))%mo+mo)%mo;
				}
		for (int i=1;i<=G.n;++i)
			for (EDGE *ei=G.last[i];ei;ei=ei->las)
				if (i<ei->to){
					ll d1=deg[i]+deg[ei->to]-2;
					(r+=d1*(d1-1)%mo*(d1-2)%mo*inv2)%=mo;
				}
	}
	return r;
}
template <int _N,int _M>
void trans(Graph<_N,_M> &G,Graph<_N,_M> &F){
	F.init(G.ne/2);
	for (int i=1;i<=G.n;++i)
		for (EDGE *ei=G.last[i];ei;ei=ei->las){
			int u=(ei-G.e>>1)+1;
			for (EDGE *ej=ei->las;ej;ej=ej->las){
				int v=(ej-G.e>>1)+1;
				F.link(u,v),F.link(v,u);
			}
		}
}
int fa[12],in[12],out[12],nowdfn,re[12],num[12];
template<int _N,int _M>
void getdfn(int x,Graph<_N,_M> &G){
	in[x]=++nowdfn;
	re[nowdfn]=x;
	for (EDGE *ei=G.last[x];ei;ei=ei->las)
		if (ei->to!=fa[x])
			fa[ei->to]=x,getdfn(ei->to,G);
	out[x]=nowdfn;
}
template<int _N,int _M>
void find_st(int k,int n,Graph<_N,_M> &G,Graph<_N,_M> &S,int &res){
	int x=re[k];
	if (k>G.n){
		if (n<G.n)
			(res+=ut_w[ut_hash(S,n,0)])%=mo;
		return;
	}
	find_st(out[x]+1,n,G,S,res);
	num[x]=++n;
	S.link(num[fa[x]],num[x]);
	S.link(num[x],num[fa[x]]);
	find_st(k+1,n,G,S,res);
	S.ne-=2;
	S.last[num[fa[x]]]=S.last[num[fa[x]]]->las;
	S.last[num[x]]=S.last[num[x]]->las;
}
int calcw(int t){
	int key=rt_to_ut(t);
	if (ut_w.find(key)!=ut_w.end()){
		sw[t]=ut_sw[key];
		return ut_w[key];
	}
	L[0].init(siz[t]);
	int tot;
	build_ut(1,t,tot=1,L[0]);
	int now=0,las=1;
	for (int i=1;i<=k-4;++i){
		swap(now,las);
		trans(L[las],L[now]);
	}
	ll res=calc234(L[now],4);
	for (int i=0;i<son[t].size();++i)
		(sw[t]+=sw[son[t][i]])%=mo;
	T.init(siz[t]);
	build_ut(1,t,tot=1,T);
	nowdfn=0,fa[1]=0,getdfn(1,T);
	S.init(siz[t]);
	num[1]=1,find_st(2,1,T,S,sw[t]);
	res=(res-sw[t]+mo)%mo;
	ut_sw[key]=(sw[t]+=res)%=mo;
	return ut_w[key]=res;
}
//Section:Calc t
int t[N];
int f[N][1300];
void dp(int x,int fa){
	int d=0;
	for (EDGE *ei=G.last[x];ei;ei=ei->las)
		if (ei->to!=fa)
			dp(ei->to,x),++d;
	static ll g[1024];
	for (int j=1;j<=cnt;++j){
		if (son[j].size()>d){f[x][j]=0;continue;}
		int p=son[j].size()-lf[j];
		memset(g,0,sizeof(ll)*(1<<p));
		g[0]=1;
		for (EDGE *ei=G.last[x];ei;ei=ei->las)
			if (ei->to!=fa){
				int y=ei->to;
				for (int i=(1<<p)-1;i>=0;--i)
					for (int k=0;k<p;++k)
						if (!(i>>k&1)){
							int id=son[j][lf[j]+k];
							(g[i|1<<k]+=(ll)g[i]*f[y][id])%=mo;
						}
			}
		f[x][j]=(ll)g[(1<<p)-1]*C(d-p,lf[j])%mo*fac[lf[j]]%mo*same[j]%mo;
		(t[j]+=f[x][j])%=mo;
	}
}
int main(){
	scanf("%d%d",&n,&k);
	initC(max(n,k+1));
	G.init(n);
	for (int i=1;i<n;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		G.link(u,v);
		G.link(v,u);
	}
	if (k<=4){
		printf("%d\n",calc234(G,k));
		return 0;
	}
	for (int i=1;i<=k+1;++i)
		grt(0,i,0,0);
	for (int i=1;i<=cnt;++i)
		w[i]=calcw(i);
	dp(1,0);
	ll ans=0;
	for (int i=1;i<=cnt;++i)
		(ans+=(ll)w[i]*t[i])%=mo;
	printf("%lld\n",ans);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章