zoukankan      html  css  js  c++  java
  • bzoj 2785 jzoj 2755 【2012东莞市选】树的计数(计数+dp):

    Description:

    给出两个整数nd,求出有n个节点并且两个节点间最长距离为d的标号树的个数。

    标号树即是树上每个结点都标有一个不同的编号。

    (n<=50)

    题解:

    假设直径是偶数,虽然直径可以有很多条,但是直径的中点是确定的。

    奇数也差不多,只是中间那条边是定的。

    所以设(g[i][j])表示表示(i)个点,定根有标号树,深度是(j)的方案数。

    然而这个东西dp不了:通常的转移是枚举它的一个子树使深度是(j-1),但是这样怎么去重都不行。

    所以设(f[i][j])表示深度不超过(j)的版本的。

    这时,一个点的所有子树都是类似的,那么只要保证选的那个子树含有标号最小的点即可。

    注意要预先选出根的颜色,但是(g[n-x][j])又给根分配了颜色,所以要除以n-x。

    转移:(g[i][j]=sum_{x=0}^{i}g[x][j-1]*g[i-x][j]*C_{i-2}^{x-1}*n/(n-x))

    最后统计答案:

    (w=d/2)

    直径是偶数:
    相当于要有至少两棵=d/2-1的子树,那么用总数-只有一棵的

    (Ans=g[n][w]-sum C_{n}^x*f[x][w-1]*g[n-x][w-1])

    直径是奇数的:
    一条边的两边都要=d/2,组合数保证最小的点在一边即可。

    (Ans=sum C_{n-1}^{x-1}*f[x][w]*f[n-x][w])

    没有取模,需要高精度。

    Code:

    #include<bits/stdc++.h>
    #define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
    #define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
    #define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
    #define ll long long
    #define pp printf
    #define hh pp("
    ")
    using namespace std;
    
    const int N = 55;
    
    int n, d;
    
    const int m = 10;
    
    const ll W = 1e8;
    
    struct P {
    	ll a[m + 2];
    	P() {
    		fo(i, 0, m) a[i] = 0;
    	}
    };
    
    P operator + (P a, P b) {
    	fo(i, 0, m) a.a[i] += b.a[i];
    	fo(i, 0, m) {
    		a.a[i + 1] += a.a[i] / W;
    		a.a[i] %= W;
    	}
    	return a;
    }
    
    P operator - (P a, P b) {
    	fo(i, 0, m) a.a[i] -= b.a[i];
    	fo(i, 0, m) if(a.a[i] < 0) {
    		a.a[i] += W;
    		a.a[i + 1] --;
    	}
    	return a;
    }
    
    P operator * (P a, P b) {
    	P c = P();
    	fo(i, 0, m) fo(j, 0, m - i)
    		c.a[i + j] += a.a[i] * b.a[j];
    	fo(i, 0, m) {
    		c.a[i + 1] += c.a[i] / W;
    		c.a[i] %= W;
    	}
    	return c;
    }
    
    P operator * (P a, ll b) {
    	fo(i, 0, m) a.a[i] *= b;
    	fo(i, 0, m) {
    		a.a[i + 1] += a.a[i] / W;
    		a.a[i] %= W;
    	}
    	return a;
    }
    
    P operator / (P a, ll b) {
    	ll y = 0;
    	fd(i, m, 0) {
    		a.a[i] += y * W;
    		y = a.a[i] % b;
    		a.a[i] /= b;
    	}
    	return a;
    }
    
    void print(P a) {
    	int a0 = m;
    	while(a0 >= 0 && !a.a[a0]) a0 --;
    	if(a0 < 0) {
    		pp("0");
    	} else {
    		pp("%lld", a.a[a0]);
    		fd(i, a0 - 1, 0) printf("%08lld", a.a[i]);
    	}
    	hh;
    }
    
    P c[N][N], f[N][N], g[N][N];
    
    P ans[N][N];
    
    int main() {
    	n = 50;
    	fo(i, 0, n) {
    		c[i][0].a[0] = 1;
    		fo(j, 1, i) c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
    	}
    	fo(j, 0, n) f[1][j].a[0] = g[0][j].a[0] = 1;
    	fo(i, 2, n) {
    		fo(j, 1, n) {
    			fo(x, 1, i - 1) {
    				f[i][j] = f[i][j] + f[x][j - 1] * f[i - x][j] * c[i - 2][x - 1] * i / (i - x);
    			}
    		}
    	}
    	fo(i, 0, n) {
    		fo(j, 1, n) g[i][j] = f[i][j] - f[i][j - 1];
    		g[i][0] = f[i][0];
    	}
    	for(n = 1; n <= 50; n ++) {
    		if(n == 1) ans[n][0].a[0] = 1;
    		for(d = 1; d <= n; d ++) {
    			int w = d / 2;
    			if(d & 1) {
    				fo(x, 1, n)	ans[n][d] = ans[n][d] + g[x][w] * g[n - x][w] * c[n - 1][x - 1];
    			} else {
    				ans[n][d] = g[n][w];
    				fo(x, 0, n) ans[n][d] = ans[n][d] - g[x][w - 1] * f[n - x][w - 1] * c[n][x];
    			}
    		}
    	}
    	while(scanf("%d %d", &n, &d) != EOF)
    		print(ans[n][d]);
    }
    
  • 相关阅读:
    JDBC 处理sql查询多个不确定参数
    网页跳转方法总结
    图片上传,直接在网页中显示(支持IE,谷歌,火狐浏览器)
    Oracle报 ORA-00054资源正忙的解决办法
    js对cookie的操作:读、写、删
    认识SignalR
    sql 查询结果用逗号分隔到一列里
    异步编程之await的使用
    应用程序池
    判断list重复扩展方法
  • 原文地址:https://www.cnblogs.com/coldchair/p/12252233.html
Copyright © 2011-2022 走看看