1. 程式人生 > >P3240 [HNOI2015]實驗比較 樹形DP

P3240 [HNOI2015]實驗比較 樹形DP

大於 digi tor ufs 而且 bits 整數 operator 質量

\(\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} |1\leq i\leq 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]\times f[v][k]\times C_{i?1}^{j?1}\times 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\n", ans);
    return 0;
}

P3240 [HNOI2015]實驗比較 樹形DP