传送门
题解:
如果是一棵树就是经典的树上揹包傻逼题。
如果是仙人掌,需要特殊处理环的情况。
std只能做点仙人掌(每个点在至多一个简单环中)。
我的做法可以做边仙人掌(每条边在至多一个简单环中)复杂度和std相同,而且常数还比std小。
首先给仙人掌任意定一个根。
这里给一个子仙人掌的通用定义,防止有读者不清楚。
一个仙人掌图以为根的时候,的子仙人掌定义如下:
删去所有到的简单路径能够经过的点(除了)之后,所在的连通块即为以为根的子仙人掌。
这里给出一个笔者自己yy的定义:环根,对于一个环,最低的,且的子仙人掌包含这个环的点称为这个环的环根。
现在定为根。
现在考虑定义表示在的子仙人掌中选择个点(必选)的方案数。
答案是所有之和?并不,我们发现这忘记了选择环上不包含环根的若干连通块的方案数。这个玩意可以在环上转移的时候顺便算一下。
首先对于一个环,我们现在考虑已经算出了除了环根的所有点的,对于环根,我们已经算出了不包含环上的点的。
我们考虑搞出一个数组,其中表示在该子仙人掌中选择个点,加上环根之后能够连通的方案数。如果搞出了,则再算出就是一个多项式乘法的事情。
显然我们转移需要分两种情况,选择了环上的所有点,选择了一个前缀和一个后缀(都允许为空),且两者不连通。
选择了环上所有点的情况可以直接揹包dp出来(其实也是一个多项式乘法)。
在这过程中可以算出恰好选择了某个前缀的所有点方案数。
现在需要算前后缀的组合,直接暴力枚举是。可以用前缀和优化到
最后一点,考虑不选环根,再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;
}