• [HEOI2013]SAO


    [HEOI2013]SAO

      这道题是个不错的计数题,考察了调换求和顺序再前缀和优化,难点在状态设计,比较考察思维。
      一句话题意:给你一棵数,树边为有向边,求其拓扑序数。
      对DAG求拓扑数是一个NP问题,但是这里保证是一棵树,所以我们可以用树形DP来求解。
      状态的设计上,光设结点编号(u)不够,还需要设计一维(i)表示结点(u)在以(u)为根的子树中的拓扑序的第(i)位,这样我们就可以写转移方程了。
      对于(u ightarrow v)

    [F'[u][k] = Sigma_{vin son} F[u][i] imes F[v][j] imes binom{k-1}{i-1} imes binom{size_u+size_v-k}{size_u-i},i leq k leq i+j-1 ]

      对于(u leftarrow v)

    [F'[u][k] = Sigma_{vin son} F[u][i] imes F[v][j] imes binom{k-1}{i-1} imes binom{size_u+size_v-k}{size_u-i},i+j leq k leq size_v+i ]

      目标:

    [Sigma_{i=1}^{N} F[1][i] ]

      (所有结点的下标自动+1)
      发现三重循环铁定不行,调换下(j)(k)的顺序,发现(F[v][j])可以前缀和处理,削掉一维。
      事实上这样做后整体的复杂度由( ext{O}(N^3))降为( ext{O}(N^2))。(仔细研读下面的代码发现实际上处理次数为点对数)
      细节看码。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define ll long long
    #define INF (1 << 30)
    #define chkmax(a, b) a = max(a, b)
    #define chkmin(a, b) a = min(a, b);
    
    inline int read() {
    	int w = 0, f = 1; char c;
    	while (!isdigit(c = getchar())) f = c == '-' ? -1 : f;
    	while (isdigit(c)) w = (w << 3) + (w << 1) + (c ^ 48), c = getchar();
    	return w * f;
    }
    
    inline int read_ch() {
    	char c;
    	while (c = getchar(), c != '>' && c != '<');
    	return c == '<';
    }
    
    const int maxn = 1000 + 5;
    const int MOD = 1e9 + 7;
    
    struct Edge {
    	int v, w, pre;
    } e[maxn << 1];
    int m, G[maxn];
    void clear() {
    	m = 0;
    	memset(G, -1, sizeof(G));
    }
    void add(int u, int v, int w) {
    	e[m++] = (Edge){v, w, G[u]};
    	G[u] = m-1;
    }
    
    int T, N;
    int f[maxn][maxn], g[maxn], C[maxn][maxn];
    
    void inc(int &a, int b) {
    	a += b;
    	if (a >= MOD) a -= MOD;
    }
    
    int dec(int a) {
    	if (a < 0) a += MOD;
    	return a;
    }
    
    void init() {
    	C[0][0] = 1;
    	for (register int i = 1; i <= 1000; i++) {
    		C[i][0] = 1;
    		for (register int j = 1; j <= i; j++)
    			C[i][j] = (C[i-1][j] + C[i-1][j-1]) % MOD;
    	}
    }
    
    int size[maxn];
    
    void dfs(int u, int fa) {
    	size[u] = 1;
    	f[u][1] = 1;
    	for (register int i = G[u]; ~i; i = e[i].pre) { \ 这里实际上相当于u<->v之间的拓扑序合并起来
    		int v = e[i].v;
    		if (v == fa) continue;
    		dfs(v, u);
    		memcpy(g, f[u], sizeof(g));
    		memset(f[u], 0, sizeof(f[u]));
    		if (e[i].w) {
    			for (register int i = 1; i <= size[u]; i++)
    				for (register int k = i; k <= i+size[v]-1; k++)
    					inc(f[u][k], (ll)g[i] * dec(f[v][size[v]]-f[v][k-i]) % MOD * C[k-1][i-1] % MOD * C[size[u]+size[v]-k][size[u]-i] % MOD);
    		} else {
    			for (register int i = 1; i <= size[u]; i++)
    				for (register int k = i+1; k <= size[v] + i; k++)
    					inc(f[u][k], (ll)g[i] * dec(f[v][k-i]) % MOD * C[k-1][i-1] % MOD * C[size[u]+size[v]-k][size[u]-i] % MOD);
    		}
    		size[u] += size[v];
    	}
    	for (register int i = 1; i <= size[u]; i++) inc(f[u][i], f[u][i-1]);
    }
    
    int main() {
    	init();
    	T = read();
    	while (T--) {
    		N = read();
    		clear();
    		for (register int i = 1; i < N; i++) {
    			int u = read()+1, opt = read_ch(), v = read()+1;
    			add(u, v, opt); add(v, u, !opt);
    		}
    
    		dfs(1, 1);
    
    		printf("%d
    ", f[1][N]);
    	}
    
    	return 0;
    }
    
  • 相关阅读:
    内存碎片
    《大规模分布式存储系统》笔记——单机存储系统、分布式系统
    数据库的范式
    一把剪刀看懂git reset 和它的三个参数
    如何判断一个链表是否有环?以及对一些文章的错误的看法
    自由树的计数 Labeled unrooted tree counting
    C语言里的指针探析——type *name[] 在函数参数里面,是一个二维指针
    CSAPP(深入理解计算机系统)读后感
    VIM一些常用命令,方法,配置
    Latex 常用知识点存档
  • 原文地址:https://www.cnblogs.com/ac-evil/p/11402785.html
Copyright © 2020-2023  润新知