【Codeforces】 CF917D Stranger Trees

题目链接

CF方向
Luogu方向

题目解法

一个显然的转化是:恰好 k k k 条边不好求,所以把 恰好 转化成 至少,然后进行二项式反演
f i f_i fi 为恰好 k k k 条边 . . . ... ... g i g_i gi 为至少 k k k 条边 . . . ... ...
那么 f i = ∑ j = i n − 1 g j ( − 1 ) j − i ( j i ) f_i=\sum\limits_{j=i}^{n-1}g_j(-1)^{j-i}\binom{j}{i} fi=j=in1gj(1)ji(ij)
这一部分的时间复杂度是 O ( n 2 ) O(n^2) O(n2)

考虑求解 g g g,可以发现保留的边数和连通块个数是可以对应的,所以接下来只对连通块考虑
这里有一个结论是:如果 m m m 个连通块的大小分别是 s i z 1 , s i z 2 , . . . , s i z m siz_1,siz_2,...,siz_m siz1,siz2,...,sizm,且每个点都有编号,则把它们构成生成树的方案数为 n m − 2 ∏ s i z i n^{m-2}\prod siz_i nm2sizi
证明可以见 oiwiki-prufer 中的 图连通方案数

我们发现直接把上面的式子进行 d p dp dp 的时间复杂度是 O ( n 3 ) O(n^3) O(n3) 的,即令 d p i , j , k dp_{i,j,k} dpi,j,k 表示在 i i i 的子树中选出了 j j j 个连通块, i i i 所在连通块有 k k k 个点的方案数

我们考虑 ∏ s i z i \prod siz_i sizi 的组合意义,即在每个连通块中选出一个点的方案数
这样就可以令 d p i , j , 0 / 1 dp_{i,j,0/1} dpi,j,0/1 表示在 i i i 的子树中,选出了 j j j 个连通块, i i i 所在连通块是否选过点的方案数
转移比较好转移,根据背包 d p dp dp 的时间复杂度分析,可以做到 O ( n 2 ) O(n^2) O(n2)

#include 
using namespace std;
const int N=110,P=1e9+7;
typedef long long LL;
int n,g[N],f[N],C[N][N];
int dp[N][N][2],t[N][2],siz[N];
int e[N<<1],ne[N<<1],h[N],idx;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
inline void inc(int &x,LL y){
	x=(x+y)%P;
	if(x<0) x+=P;
}
void dfs(int u,int fa){
	dp[u][1][0]=dp[u][1][1]=1,siz[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v==fa) continue;
        dfs(v,u);
		for(int j=1;j<=siz[u]+siz[v];j++) t[j][0]=t[j][1]=0; 
		for(int j=1;j<=siz[u];j++) for(int k=1;k<=siz[v];k++){
			inc(t[j+k][0],1ll*dp[u][j][0]*dp[v][k][1]);
			inc(t[j+k][1],1ll*dp[u][j][1]*dp[v][k][1]);
			inc(t[j+k-1][0],1ll*dp[u][j][0]*dp[v][k][0]);
			inc(t[j+k-1][1],(1ll*dp[u][j][0]*dp[v][k][1]+1ll*dp[u][j][1]*dp[v][k][0]));
		}
		siz[u]+=siz[v];
		for(int j=1;j<=siz[u];j++) dp[u][j][0]=t[j][0],dp[u][j][1]=t[j][1];
	}
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int qmi(int a,int b){
    if(b<0) return qmi(a,P-2);
    int res=1;
    for(;b;b>>=1){
        if(b&1) res=1ll*res*a%P;
        a=1ll*a*a%P;
    }
    return res;
}
int main(){
	n=read();
    memset(h,-1,sizeof(h));
	for(int i=1;i<n;i++){
		int x=read(),y=read();
		add(x,y),add(y,x);
	}
	dfs(1,-1);
	for(int i=1;i<=n;i++) g[n-i]=1ll*dp[1][i][1]*qmi(n,i-2)%P;
	C[0][0]=1;
	for(int i=1;i<=n;i++) for(int j=0;j<=i;j++) C[i][j]=(!j||i==j)?1:(C[i-1][j-1]+C[i-1][j])%P;
	for(int i=0;i<n;i++)
		for(int j=i,neg=1;j<n;j++,neg*=-1)
			inc(f[i],1ll*g[j]*neg*C[j][i]);
	for(int i=0;i<n;i++) printf("%d ",f[i]);puts("");
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}
/*
f[i][j][0/1]: 在i的子树中,分成了j个连通块,i连通块内是否选过的方案数
*/

你可能感兴趣的:(Codeforces,算法)