CF512D Fox And Travelling
题意
给定一张图,每次只能选择一个与之相连的点中至多有一个点未选择的点,然后选择它。求有序选择 \(k\) 个点的方案数,对 \(k\in[0,n]\) 求解。
Solution
考虑选择点可以看成是删点,那么每次只有度数小于等于 \(1\) 的点可以删掉。这样的话容易想到就是一个环(边双)是删不掉的,于是我们可以把边双缩♂起来,然后打个标记说这个点是删不了的。
那么问题就转化成:给定一个森林,有些点可以删,有些点不能删,并且只能删叶子节点,求删去 \(k\) 个点的方案数。看了一眼这个东西就很可以大炮的样子。
于是我们来大炮。令 \(dp_{i,j}\) 表示以 \(i\) 为根的子树中删去 \(j\) 个节点的方案数。这样子的话,由于方案之间是独立的,可以通过简单的组合数学合并两个 \(dp\) 值:
这样的话,我们只需要考虑根什么时候可以删掉就可以了。当前的根可以删当且仅当它的所有子树都删空了。那如果子树中有不可删除的点,那当前的根不能删除,直接合并即可。如果能删空子树,那么可以删去的点加一。
然后由于是森林,把每个连通块做完之后直接合并起来就好了。
出大问题,我们实际上在无根树中随便选了一个根,而这个根在只剩下一棵子树的时候就可以删去,可以想到的是,删去了根,势必导致可以直接从上往下删,没法正常的大炮了。
一个解决办法是选取不能删的点作为根,但是并不是每个连通块都有这样不能删的点。可能这个 *2900 的难度就在这里?
另一个方法是考虑在上一个方法的基础上,对于无根树的 Case,直接对每个点都跑一次树形 dp,然后加起来。然后除以 \(siz-k\),其中 \(siz\) 表示无根树的大小。这个去重就是对于选中的 \(k\) 个删去的点,在以剩下的 \(siz-k\) 个点作根的时候都计算了一遍。而我们选根相当于钦定它不删/最后删。
Code
#define int long long
using namespace std;
const int MAXN=110;
const int MOD=1e9+9;
int ksm(int a,int p){
int ret=1;while(p){
if(p&1) ret=ret*a%MOD;
a=a*a%MOD; p>>=1;
}return ret;
}int inv(int x){return ksm(x,MOD-2);}
vector<int> te[MAXN];
int u[MAXN*MAXN],v[MAXN*MAXN],cnt,col[MAXN],in[MAXN],siz[MAXN];
int dfn[MAXN],low[MAXN],stk[MAXN],top,tot;
int n,m;
void Tarjan(int x,int fa){
dfn[x]=low[x]=++tot;stk[++top]=x;in[x]=1;
for(int s:te[x]){
if(s==fa) continue;
if(!dfn[s]) Tarjan(s,x),low[x]=min(low[x],low[s]);
else if(in[s]) low[x]=min(low[x],dfn[s]);
}if(dfn[x]==low[x]){
cnt++;do{
in[stk[top]]=0;siz[cnt]++;
col[stk[top]]=cnt;
}while(stk[top--]!=x);
}
}
int mp[MAXN][MAXN],C[MAXN][MAXN];
vector<int> e[MAXN];
void prework(){
rep(i,1,n) if(!dfn[i]) Tarjan(i,0);
rep(i,1,m){
int uu=col[u[i]],vv=col[v[i]];
if(uu==vv||mp[uu][vv]) continue;
mp[uu][vv]=mp[vv][uu]=1;
e[uu].pb(vv);e[vv].pb(uu);
}
C[0][0]=1;rep(i,1,n){
C[i][0]=1;rep(j,1,i)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
}
}
int dp[MAXN][MAXN],vis[MAXN],sonsiz[MAXN];
bool dfs(int x,int fa){
memset(dp[x],0,sizeof(dp[x]));
dp[x][0]=1;
bool ok=1;sonsiz[x]=1;
for(int s:e[x]){
if(s==fa) continue;
ok=ok&dfs(s,x);sonsiz[x]+=sonsiz[s];
per(a,sonsiz[x],0){int res=0; rep(b,0,a)
res=(res+dp[x][a-b]*dp[s][b]%MOD*C[a][b]%MOD)%MOD;
dp[x][a]=res;}
}ok=ok&(siz[x]==1);
if(ok) dp[x][sonsiz[x]]=dp[x][sonsiz[x]-1];
return ok;
}
vector<int> nd;
int check(int st){
queue<int> q;q.push(st);
vis[st]=1;int ret=-1;
while(!q.empty()){
int x=q.front();q.pop();
nd.pb(x);
for(int s:e[x]){
if(vis[s]) continue;
vis[s]=1;q.push(s);
if(siz[s]>1) ret=s;
}
}return ret;
}
int tmp[MAXN];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
rep(i,1,m){
cin>>u[i]>>v[i];
te[u[i]].pb(v[i]);te[v[i]].pb(u[i]);
}prework();
dp[0][0]=1;
rep(i,1,cnt) if(!vis[i]){
nd.clear();memset(tmp,0,sizeof(tmp));
int root=check(i),sz=(int)nd.size();
if(root==-1){
for(int rt:nd){
dfs(rt,0);
rep(j,0,sz) tmp[j]=(tmp[j]+dp[rt][j])%MOD;
}
rep(j,0,sz-1) tmp[j]=tmp[j]*inv(sz-j)%MOD;
}else{
dfs(root,0);
rep(j,0,sz) tmp[j]=dp[root][j];
}
per(a,n,0){int res=0; rep(b,0,a)
res=(res+dp[0][a-b]*tmp[b]%MOD*C[a][b]%MOD)%MOD;
dp[0][a]=res;}
}
rep(i,0,n) cout<<dp[0][i]<<'\n';
return 0;
}