• 6494. 【GDOI2020模拟03.08】勘探


    题目

    求大小为(n)的直径为(L)(指边数)的无标号无根树数量。


    思考历程

    一开始乱推了一波,后来发现会算重。
    于是弃疗。


    正解

    如果(L)为偶数,所有直径的中点是一样的;如果(L)为奇数,所有直径的中边是一样的。
    这个挺好证明的。

    把无根树定了根之后就好做很多了。先考虑(L)为奇数,把树从中边切开,形成两棵树,这两棵树都要满足最大深度为恰好为(frac{L-1}{2})
    (g_{i,j})为大小为(i),最大深度恰好为(j)的树的个数、
    这个东西不好算,就设(f_{i,j})为大小为(i),最大深度至多为(j)的1个数。
    转移的时候枚举新增子树大小(k)及其个数(t)
    需要注意的是转移的时候循环变量的枚举顺序,(j)放最外层,(k)放次外层。可以这么理解:把(j)不同的每一层看成个生成函数,计算大小为(k)的子树的贡献的时候给整一层计算贡献,不然容易算重。
    搞完之后,枚举左右子树大小即可以求出答案(注意大小相等的情况)。

    如果(L)为偶数,相当于以中点为根的树中,根有至少两个儿子的子树的深度恰好为(frac{L}{2}-1)(已经减去了根到儿子的边)。同样像上面一样求出(f)(g)
    计算答案考虑总体减不合法,总体是(g_{n,frac{L}{2}}),不合法的就是根只有一个儿子的子树深度恰好为(frac{L}{2}-1),枚举其大小(i),贡献即为(g_{i,frac{L}{2}-1}f_{n-i,frac{L}{2}-1})


    代码

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 210
    #define ll long long
    int n,L,mo;
    ll inv[N];
    ll f[N][N],g[N][N];
    void dp(int n,int lim){
    	for (int j=0;j<=lim;++j)
    		f[1][j]=1;
    	for (int j=1;j<=lim;++j)
    		for (int k=1;k<n;++k){
    			ll tmp=f[k][j-1];
    			for (int i=n;i>=2 && i>k;--i){
    //				if (i==5)
    //					printf("i=5
    ");
    				ll s=tmp;
    				for (int t=1;k*t<i;++t,s=s*(tmp+t-1+mo)%mo*inv[t]%mo)
    					(f[i][j]+=f[i-k*t][j]*s)%=mo;
    			}
    		}
    	for (int i=1;i<=n;++i){
    		g[i][0]=f[i][0];
    		for (int j=1;j<=lim && j<i;++j)
    			g[i][j]=(f[i][j]-f[i][j-1]+mo)%mo;
    	}
    //	for (int i=1;i<=n;++i,printf("
    "))
    //		for (int j=0;j<=lim && j<i;++j)
    //			printf("%d ",g[i][j]);
    }
    int main(){
    	freopen("exploit.in","r",stdin);
    	freopen("exploit.out","w",stdout);
    	scanf("%d%d%d",&n,&L,&mo);
    	inv[1]=1;
    	for (int i=2;i<=n;++i)
    		inv[i]=(mo-mo/i)*inv[mo%i]%mo;
    	int lim=L/2;
    	dp(n,lim);
    	ll ans=0;
    	if (L&1){
    		for (int i=lim;n-i>=lim && i<=n-i;++i){
    			if (i==n-i)
    				ans+=g[i][lim]*(g[i][lim]+1)%mo*inv[2]%mo;
    			else
    				ans+=g[i][lim]*g[n-i][lim]%mo;
    		}
    		ans%=mo;
    	}
    	else{
    		ans=g[n][lim];
    		if (lim>=2)
    			for (int i=lim;i<n;++i)
    				ans-=g[i][lim-1]*f[n-i][lim-1]%mo;
    		ans=(ans%mo+mo)%mo;
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    总结

    见到无根树的时候要想办法给它定个根。
    什么直径的中点中边,或者重心之类的。

  • 相关阅读:
    jq 判断单选是否选中
    C# Split 分隔符为字符串及空格的处理
    MSSQL中的bit类型
    js 判断textarea 不为空
    [转]C#中out 及 ref 区别
    [转]asp.net中时间差的问题
    [转]C# Array 数组 及 一些操作
    Addr、@运算符与Pointer类型
    关于Pascal语言中的分号
    关于以后的文章
  • 原文地址:https://www.cnblogs.com/jz-597/p/12459440.html
Copyright © 2020-2023  润新知