傳送門
題解:
如果是一棵樹就是經典的樹上揹包傻逼題。
如果是仙人掌,需要特殊處理環的情況。
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;
}