zoukankan      html  css  js  c++  java
  • CF1097G Vladislav and a Great Legend

    一、题目

    点此看题

    二、解法

    其实这题挺难的,而且我觉得网上的题解讲的有点不清楚 (...)

    看到题目要求的是 (f(x)^k) 并且 (kleq 200),搞一个傻逼斯特林反演即可:

    [sum_{x}f(x)^k=sum_xsum_{i=1}^k S(k,i)cdot i!cdot {f(x)choose i}=sum_{i=1}^k S(k,i)cdot icdot sum_x{f(x)choose i} ]

    问题变成了对于所有虚树,求出恰好选 (i) 条边的方案数。这个问题并不好做,我们严格遵循 性质->状态->dp 的思路:

    首先分析性质:虚树是由点集生成的,所以我们计数时要考虑点的状态(以点为主体);再此基础上我们统计选出 (i) 条边的方案,这两步计数是分开的,但是我们可以放在一个 (dp) 中处理。

    然后分析状态:我们想维护选出 (i) 条边的方案,而树上 (dp) 基本上是从子树合并这种情况入手的,我们考虑两棵虚树如何合并。我们已知的是 (u) 为根的原始树选出 (j) 条边的方案,还已知 (v) 为根的子树选出 (k) 条边的方案。相当于我们把某序列分成两部分,已知各部分的组合方案要求总数的组合方案,那么由枚举法可知把 (j+k=i) 的情况相乘然后累加上去即可,这样我们完成了虚树的合并。

    最后设计 (dp):设 (f[u][i]) 表示以 (u) 为根的所有虚树中选出 (i) 条边的方案数,虚树合并转移:

    [g[i+j]leftarrow f[u][i] imes f[v][j],g[i+j+1]leftarrow f[u][i] imes f[v][j] ]

    还有一个部分是考虑点的状态,在合并完之后我们考虑算出 以 (u) 为根的原始虚树(+)(u) 为根的 (v) 的虚树(+)(u) 为根的合并虚树即可。第一三种情况好算,第二种情况讨论一下父边的是否选即可。

    最后证明一下时间复杂度,合并转移时的边界是 (min(siz[u],k) imes min(siz[v],k)),相当于从 (u) 子树中选出 ( t dfs) 序后 (k) 个和 (v) 子树中 ( t dfs) 序的前 (k) 个来算。考虑单个点的贡献,发现它只会和 ( t dfs) 序相邻的左右 (2k) 个点转移并产生 (1) 的贡献,所以时间复杂度 (O(nk))

    三、总结

    这道题说明组合数也是可合并的,不要被吓到了,按照思路步步分析即可。

    对于第二维是背包合并的树 (dp),设第二维大小是 (k),可以知道复杂度大小是 (O(nk)) 的。

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int N = 205;
    const int M = 100005;
    const int MOD = 1e9+7;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,k,tot,f[M],ans[M],siz[M],dp[M][N],g[N],S[N][N];
    struct edge
    {
    	int v,next;
    }e[2*M];
    void add(int &x,int y) {x=(x+y)%MOD;}
    void dfs(int u,int fa)
    {
    	dp[u][0]=1;siz[u]=1;
    	for(int x=f[u];x;x=e[x].next)
    	{
    		int v=e[x].v;
    		if(v==fa) continue;
    		dfs(v,u);
    		for(int i=0;i<=min(siz[u],k);i++)
    			for(int j=0;j<=min(siz[v],k);j++)
    			{
    				if(i+j>k) break;
    				add(g[i+j],dp[u][i]*dp[v][j]);
    				add(ans[i+j],dp[u][i]*dp[v][j]);
    				add(g[i+j+1],dp[u][i]*dp[v][j]);
    				add(ans[i+j+1],dp[u][i]*dp[v][j]);
    			}
    		siz[u]+=siz[v];
    		for(int i=0;i<=k;i++)
    		{
    			add(dp[u][i],g[i]),g[i]=0;
    			add(dp[u][i],dp[v][i]);
    			if(i) add(dp[u][i],dp[v][i-1]);
    		}
    	}
    }
    signed main()
    {
    	n=read();k=read();
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read();
    		e[++tot]=edge{v,f[u]},f[u]=tot;
    		e[++tot]=edge{u,f[v]},f[v]=tot;
    	}
    	dfs(1,0);
    	S[0][0]=1;int res=0,fac=1;
    	for(int i=1;i<=k;i++)
    		for(int j=1;j<=i;j++)
    			S[i][j]=(S[i-1][j-1]+S[i-1][j]*j)%MOD;
    	for(int i=1;i<=k;i++,fac=fac*i%MOD)
    		add(res,S[k][i]*fac%MOD*ans[i]);
    	printf("%lld
    ",res);
    }
    
  • 相关阅读:
    时钟展频技术能有效降低EMI,深入讲解展频发生器!
    24:购物单
    22:按照字典输出字符串
    21:句子逆序
    20:字符反转
    19:ReverseNumber数字颠倒
    DNS原理及其解析过程
    18:字符个数统计
    17:不重复整数提取NoRepeatNum
    \s+ split替换
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15042761.html
Copyright © 2011-2022 走看看