【校內模擬】仙人球(樹上揹包轉仙人掌上揹包)

傳送門


題解:

如果是一棵樹就是經典的樹上揹包傻逼題。

如果是仙人掌,需要特殊處理環的情況。

std只能做點仙人掌(每個點在至多一個簡單環中)。

我的做法可以做邊仙人掌(每條邊在至多一個簡單環中)複雜度和std相同,而且常數還比std小。

首先給仙人掌任意定一個根。

這裏給一個子仙人掌的通用定義,防止有讀者不清楚。

一個仙人掌圖以uu爲根的時候,vv的子仙人掌定義如下:
刪去所有uuvv的簡單路徑能夠經過的點(除了vv)之後,vv所在的連通塊即爲以vv爲根的子仙人掌。

這裏給出一個筆者自己yy的定義:環根,對於一個環,最低的vv,且vv的子仙人掌包含這個環的點稱爲這個環的環根。

現在定11爲根。

現在考慮定義f[u][i]f[u][i]表示在uu的子仙人掌中選擇ii個點(uu必選)的方案數。

答案是所有f[u][]f[u][*]之和?並不,我們發現這忘記了選擇環上不包含環根的若干連通塊的方案數。這個玩意可以在環上轉移ff的時候順便算一下。

首先對於一個環,我們現在考慮已經算出了除了環根的所有點的ff,對於環根,我們已經算出了不包含環上的點的ff

我們考慮搞出一個數組tptp,其中tp[i]tp[i]表示在該子仙人掌中選擇ii個點,加上環根之後能夠連通的方案數。如果搞出了tptp,則再算出ff就是一個多項式乘法的事情。

顯然我們轉移需要分兩種情況,選擇了環上的所有點,選擇了一個前綴和一個後綴(都允許爲空),且兩者不連通。

選擇了環上所有點的情況可以直接揹包dp出來(其實也是一個多項式乘法)。

在這過程中可以算出恰好選擇了某個前綴的所有點方案數。

現在需要算前後綴的組合,直接暴力枚舉是O(2)O(環長^2)。可以用前綴和優化到O()O(環長)

最後一點,考慮不選環根,再dp一次就行了。

如果直接封裝多項式乘法會好寫點。

只跑了std用時的1/2

於是可以把模數改成998244353,然後告訴環上有若干個度數爲2的點,出毒瘤題了。


代碼:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;

cs int mod=1e9+7;
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}

cs int N=5e3+7,K=1e2+7;

int n,m,k,ans;

std::vector<int> G[N];
inline void adde(int u,int v){
	G[u].push_back(v);
	G[v].push_back(u);
}

int dfn[N],ed[N],fa[N],id[N],clk;

inline void find_lp(int u){
	for(int re v=id[u]=u;ed[v]!=u;v=fa[v],id[v]=u);
}
void tarjan(int u,int p){
	dfn[u]=++clk;fa[u]=p;
	for(int re v:G[u])if(v!=p){
		if(!dfn[v])tarjan(v,u);
		else if(dfn[v]<dfn[u])find_lp(ed[v]=u);
	}
}

int f[N][K],pre[N][K],suf[N][K];
int h[N],tp[N];

inline void dp_lp(int u){
	int v=ed[u];std::vector<int> nd;
	while(v!=u)nd.push_back(v),v=fa[v];
	nd.push_back(u);int m=nd.size()-1;
	std::reverse(nd.begin(),nd.end());
	memcpy(h,f[nd[1]],sizeof(int)*(k+1));
	int delta=0;
	for(int re i=2;i<=m;++i){
		int u=nd[i];
		for(int re j=k;j;--j){h[j]=0;
			for(int re t=1;t<j;++t)Inc(h[j],mul(h[t],f[u][j-t]));
		}
		for(int re j=2;j<=k;++j)Inc(ans,h[j]),Inc(delta,h[j]);
		for(int re j=1;j<k;++j)Inc(h[j],f[u][j]);
	}
	memset(tp,0,sizeof(int)*(k+1));
	memcpy(pre[1],f[nd[1]],sizeof(int)*(k+1));
	for(int re i=2;i<=m;++i){
		int u=nd[i];
		memset(pre[i],0,sizeof(int)*(k+1));
		for(int re j=1;j<=k;++j)if(pre[i-1][j])
		for(int re t=1;t+j<=k;++t)Inc(pre[i][t+j],mul(pre[i-1][j],f[u][t]));
	}
	for(int re i=1;i<=m;++i)for(int re j=1;j<=k;++j)Inc(tp[j],pre[i][j]);
	memcpy(suf[m],f[nd[m]],sizeof(int)*(k+1));
	for(int re i=m-1;i;--i){
		int u=nd[i];
		memset(suf[i],0,sizeof(int)*(k+1));
		for(int re j=1;j<=k;++j)if(suf[i+1][j])
		for(int re t=1;t+j<=k;++t)Inc(suf[i][j+t],mul(suf[i+1][j],f[u][t]));
	}
	for(int re i=2;i<=m;++i)for(int re j=1;j<=k;++j)Inc(tp[j],suf[i][j]);
	for(int re i=2;i+2<=m;++i)for(int re j=1;j<=k;++j)Inc(pre[i][j],pre[i-1][j]);
	for(int re i=1;i+2<=m;++i){
		int *t1=pre[i],*t2=suf[i+2];
		for(int re j=1;j<=k;++j)if(t1[j])
		for(int re t=1;j+t<=k;++t)Inc(tp[j+t],mul(t1[j],t2[t]));
	}
	memcpy(h,f[u],sizeof(int)*(k+1));
	for(int re i=1;i<=k;++i)if(h[i])
	for(int re j=1;i+j<=k;++j)Inc(f[u][i+j],mul(h[i],tp[j]));
}

bool vis[N];
void dp(int u){
	vis[u]=true;f[u][1]=1;
	for(int re v:G[u])if(!vis[v]){
		dp(v);
		if(!id[u]||id[v]!=id[u]){
			for(int re i=k;i;--i)
			for(int re j=i-1;j;--j)Inc(f[u][i],mul(f[u][j],f[v][i-j]));
		}
	}
	if(ed[u])dp_lp(u);
	for(int re i=1;i<=k;++i)Inc(ans,f[u][i]);
}

signed main(){
#ifdef zxyoi
	freopen("cactus.in","r",stdin);
#endif
	scanf("%d%d%d",&n,&m,&k);
	for(int re i=1;i<=m;++i){
		int u,v;scanf("%d%d",&u,&v);
		adde(u,v);
	}
	tarjan(1,0);dp(1);
	cout<<ans<<"\n";
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章