【校内模拟】仙人球(树上揹包转仙人掌上揹包)

传送门


题解:

如果是一棵树就是经典的树上揹包傻逼题。

如果是仙人掌,需要特殊处理环的情况。

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章