Loading

CF512D Fox And Travelling

题意

给定一张图,每次只能选择一个与之相连的点中至多有一个点未选择的点,然后选择它。求有序选择 \(k\) 个点的方案数,对 \(k\in[0,n]\) 求解。

Solution

考虑选择点可以看成是删点,那么每次只有度数小于等于 \(1\) 的点可以删掉。这样的话容易想到就是一个环(边双)是删不掉的,于是我们可以把边双缩♂起来,然后打个标记说这个点是删不了的。

那么问题就转化成:给定一个森林,有些点可以删,有些点不能删,并且只能删叶子节点,求删去 \(k\) 个点的方案数。看了一眼这个东西就很可以大炮的样子。

于是我们来大炮。令 \(dp_{i,j}\) 表示以 \(i\) 为根的子树中删去 \(j\) 个节点的方案数。这样子的话,由于方案之间是独立的,可以通过简单的组合数学合并两个 \(dp\) 值:

\[dp_{i,a+b}={a+b\choose a}\times dp_{j,a}\times dp_{k,b} \]

这样的话,我们只需要考虑根什么时候可以删掉就可以了。当前的根可以删当且仅当它的所有子树都删空了。那如果子树中有不可删除的点,那当前的根不能删除,直接合并即可。如果能删空子树,那么可以删去的点加一。

然后由于是森林,把每个连通块做完之后直接合并起来就好了。

出大问题,我们实际上在无根树中随便选了一个根,而这个根在只剩下一棵子树的时候就可以删去,可以想到的是,删去了根,势必导致可以直接从上往下删,没法正常的大炮了。

一个解决办法是选取不能删的点作为根,但是并不是每个连通块都有这样不能删的点。可能这个 *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;
}
posted @ 2022-07-22 10:52  ZCETHAN  阅读(23)  评论(0编辑  收藏  举报