zoukankan      html  css  js  c++  java
  • P3240 [HNOI2015]实验比较 树形DP

    (color{#0066ff}{ 题目描述 })

    小D 被邀请到实验室,做一个跟图片质量评价相关的主观实验。实验用到的图片集一共有 (N) 张图片,编号为 (1)(N)。实验分若干轮进行,在每轮实验中,小 D会被要求观看某两张随机选取的图片, 然后小D 需要根据他自己主观上的判断确定这两张图片谁好谁坏,或者这两张图片质量差不多。 用符号”(<)“、”(>)“和”(=)“表示图片 (x)(y)(x)(y)为图片编号)之间的比较:

    如果上下文中 (x)(y) 是图片编号,则 (x<y) 表示图片 xx”质量优于“(y)(x>y) 表示图片 (x)”质量差于“(y)(x=y)表示图片 (x)(y)”质量相同“;

    也就是说,这种上下文中,”(<)“、”(>)“、”(=)“分别是质量优于、质量差于、质量相同的意思;在其他上下文中,这三个符号分别是小于、大于、等于的含义。图片质量比较的推理规则(在 x和y是图片编号的上下文中):

    (1)(x < y)等价于 (y > x)

    (2)若 (x < y)(y = z),则(x < z)

    (3)若(x < y)(x = z),则 (z < y)

    (4)(x=y)等价于 (y=x)

    (5)若(x=y)(y=z),则(x=z)

    实验中,小 D 需要对一些图片对((x, y)),给出 (x < y)(x = y)(x > y) 的主观判断。小D 在做完实验后, 忽然对这个基于局部比较的实验的一些全局性质产生了兴趣。在主观实验数据给定的情形下,定义这 (N) 张图片的一个合法质量序列为形如”(x_1) (R_1) (x_2) (R_2) (x_3) (R_3) ...... (x_{N-1}) (R_{N-1}) (x_N)“的串,也可看作是集合{ (x_i{ R_i x_{i+1} |1leq ileq N-1}}),其中 (x_i)为图片编号,(x_1),(x_2),...,(x_N)两两互不相同(即不存在重复编号),(R_i)(<)(=),”合法“是指这个图片质量序列与任何一对主观实验给出的判断不冲突。

    例如: 质量序列(3 < 1 = 2) 与主观判断”(3 > 1,3 = 2)“冲突(因为质量序列中 (3<1)(1=2),从而(3<2),这与主观判断中的 (3=2) 冲突;同时质量序列中的 (3<1) 与主观判断中的 (3>1) 冲突) ,但与主观判断”(2 = 1)(3 < 2)“ 不冲突;因此给定主观判断”(3>1)(3=2)“时,(1<3=2)(1<2=3) 都是合法的质量序列,(3<1=2)(1<2<3)都是非法的质量序列。由于实验已经做完一段时间了,小D 已经忘了一部分主观实验的数据。对每张图片 (i),小 D 都最多只记住了某一张质量不比 (i) 差的另一张图片 (K_i)。这些小 D 仍然记得的质量判断一共有 (M) 条((0 leq M leq N)),其中第(i) 条涉及的图片对为((K_{X_i}, X_i)),判断要么是(K_{X_i} < X_i) ,要么是(K_{X_i} = X_i),而且所有的(X_i)互不相同。小D 打算就以这(M) 条自己还记得的质量判断作为他的所有主观数据。

    现在,基于这些主观数据,我们希望你帮小 D 求出这 N 张图片一共有多少个不同的合法质量序列。

    我们规定:如果质量序列中出现”(x = y)“,那么序列中交换 (x)(y)的位置后仍是同一个序列。因此: (1<2=3=4<5)(1<4=2=3<5) 是同一个序列, (1 < 2 = 3)(1 < 3 = 2) 是同一个序列,而(1 < 2 < 3)(1 < 2 = 3)是不同的序列,(1<2<3)(2<1<3) 是不同的序列。由于合法的图片质量序列可能很多, 所以你需要输出答案对(10^9 + 7) 取模的结果

    (color{#0066ff}{输入格式})

    第一行两个正整数(N),(M),分别代表图片总数和小D仍然记得的判断的条数;接下来(M)行,每行一条判断,每条判断形如“(x < y)“或者“(x = y)“。

    (color{#0066ff}{输出格式})

    输出仅一行,包含一个正整数,表示合法质量序列的数目对 (10^9+7)取模的结果。

    (color{#0066ff}{输入样例})

    5 4
    1 < 2
    1 < 3
    2 < 4
    1 = 5
    

    (color{#0066ff}{输出样例})

    5
    

    (color{#0066ff}{数据范围与提示})

    不同的合法序列共5个,如下所示:

    1 = 5 < 2 < 3 < 4 
    1 = 5 < 2 < 4 < 3 
    1 = 5 < 2 < 3 = 4 
    1 = 5 < 3 < 2 < 4 
    1 = 5 < 2 = 3 < 4 
    100%的数据满足N<=100。
    

    (color{#0066ff}{ 题解 })

    首先,用ufs把等号的点缩起来(没影响),然后开始判无解

    因为每个点小于的信息最多只有一个,所以可以用并查集判断无解

    而且根据这个条件,可以连边弄出一个森林,显然我们需要一个超级源建立一棵树

    然后可以愉快的开始树形DP了

    常规的设法,(f[i])表示以i为根子树的方案

    但是发现并不行

    因为两个序列合并的时候可以是小于,还可以是相等

    也就是说对于i的子树的两个不同的节点uv,他们的子树可能存在两个节点相等的情况

    (f[u][i])表示(u)的子树里,分成(i)段(也就是共有(i-1)个小于号把质量序列分成了(i)个部分,每个部分里的图片质量相等)的方案数,然后做一次树形背包DP(当前枚举到了(u)的子节点(v),f'表示枚举到子节点(v)前的DP值):

    (f[u][i]=sum_{j,k}f'[u][j] imes f[v][k] imes C_{i−1}^{j−1} imes C_{j−1}^{k−i+j})

    后面两个是把j段和k段合并为i段的方案

    (f[u])的质量序列为(A)(f'[u])的质量序列为(B)(f[v])的质量序列为(C)

    (A)中的每一段可以只包含(B)中的一段,可以只包含(C)中的一段,也可以有(B)(C)各一段合并而成,但不能为空。特殊地,(A)的第一段只能包含节点(u)

    相当于先枚举(B)中的(j-1)段在(A)中放的位置,方案数为(C_{i-1}^{j-1}),然后把(C)中的(i-j)段放到(A)中剩下的位置,使每一段都不为空。现在(C)还剩下(k-i+j)个段,他们需要与(B)中的段合并,方案数(C_{j-1}^{k-i+j})

    最后总答案为 (sum_{i}f[n+1][i])

    #include<bits/stdc++.h>
    #define LL long long
    LL in() {
    	char ch; LL x = 0, f = 1;
    	while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    	return x * f;
    }
    const int mod = 1e9 + 7;
    const int maxn = 520;
    LL f[maxn][maxn], g[maxn], C[maxn][maxn];
    int fa[maxn], siz[maxn], du[maxn], bel[maxn];
    bool vis[maxn];
    struct node {
    	int to;
    	node *nxt;
    	node(int to = 0, node *nxt = NULL): to(to), nxt(nxt) {}
    	void *operator new(size_t) {
    		static node *S = NULL, *T = NULL;
    		return (S == T) && (T = (S = new node[1024]) + 1024), S++;
    	}
    };
    struct E {
    	int x, y, tp;
    }e[maxn];
    node *head[maxn];
    void add(int from, int to) {
    	head[from] = new node(to, head[from]);
    }
    char getch() {
    	char ch = getchar();
    	while(ch != '<' && ch != '=') ch = getchar();
    	return ch;
    }
    int n, m; 
    void predoit() {
    	C[0][0] = 1;
    	for(int i = 1; i <= n; i++) {
    		C[i][0] = 1;
    		for(int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
    	}
    }
    int findset(int x) { return x == fa[x]? fa[x] : fa[x] = findset(fa[x]); }
    void dfs(int x) {
    	siz[x] = 1, f[x][1] = 1;
    	for(node *i = head[x]; i; i = i->nxt) {
    		dfs(i->to);
    		for(int v = 1; v <= n; v++) g[v] = 0;
    		for(int v = 1; v <= siz[x] + siz[i->to]; v++)
    			for(int j = 1; j <= siz[x]; j++)
    				for(int k = 1; k <= siz[i->to]; k++) {
    					int p = k - v + j;
    					if(p < 0) continue;
    					(g[v] += f[x][j] * f[i->to][k] % mod * C[v - 1][j - 1] % mod * C[j - 1][p] % mod) %= mod;
    				}
    		for(int v = 1; v <= siz[x] + siz[i->to]; v++) f[x][v] = g[v];
    		siz[x] += siz[i->to];
    	}
    }
    
    					
    
    int main() {
    	n = in(), m = in();
    	predoit();
    	for(int i = 1; i <= n; i++) fa[i] = i;
    	for(int i = 1; i <= m; i++) e[i].x = in(), e[i].tp = getch() == '=', e[i].y = in();
    	for(int i = 1; i <= m; i++) {
    		if(!e[i].tp) continue;
    		int xx = findset(e[i].x);
    		int yy = findset(e[i].y);
    		if(xx != yy) fa[xx] = yy;
    	}
    	for(int i = 1; i <= n; i++) vis[bel[i] = findset(i)] = true;
    	for(int i = 1; i <= n; i++) fa[i] = i;
    	for(int i = 1; i <= m; i++) {
    		if(e[i].tp) continue;
    		add(bel[e[i].x], bel[e[i].y]), du[bel[e[i].y]]++;
    		int xx = findset(bel[e[i].x]), yy = findset(bel[e[i].y]);
    		if(xx == yy) return puts("0"), 0;
    		fa[xx] = yy;
    	}
    	for(int i = 1; i <= n; i++) if(!du[i] && vis[i]) add(n + 1, i);
    	dfs(n + 1);
    	LL ans = 0;
    	for(int i = 1; i <= n + 1; i++) (ans += f[n + 1][i]) %= mod;
    	printf("%lld
    ", ans);
    	return 0;
    }
    
  • 相关阅读:
    SQL Server 2005存储过程示例
    SQL Server 存储过程
    SQL Server 2005存储过程示例
    转正申请书
    SQL注入天书
    转:毕业半年,我是如何从一名程序员成长为一名项目经理
    DIMFOM
    MONSA
    GLOBSYMM
    MASSHA
  • 原文地址:https://www.cnblogs.com/olinr/p/10371920.html
Copyright © 2011-2022 走看看