1. 程式人生 > 實用技巧 >「題解」 CF149D Coloring Brackets

「題解」 CF149D Coloring Brackets

Problem

Link

題意很簡單,就是給你一串合法的括號序列(如 \(()(())\) ),給你三個染色的條件:

  1. 一個括號可以染紅色、藍色或不染色;
  2. 一對匹配的括號需要且只能將其中一個染色 ;
  3. 相鄰兩個括號顏色不能相同(但可以都不染色);

求符合條件的染色方案總數。

Solution

我們定義括號序列為 \(s\) , \(dp[i][j][u][v]\) 表示 \(s[i]\) 用顏色 \(u\)\(s[j]\) 用顏色 \(v\)\(s\)\([i,j]\) 上的方案總數。其中 \(u,v=0/1/2\)\(0\) 表示不染色,\(1\) 表示染紅色,\(2\)表示染藍色)。\(p[i]\)

表示與 \(s[i]\)\(s[i]\)為正括號)匹配的反括號的下標。

我們用 \(l,r\) 表示一段區間的左端點和右端點

  1. \(l+1=r\)

    因為 \(s\) 是合法的序列,所以經過遞迴的操作後也是合法的括號序列,即為 \(()\),而這種情況,根據條件一和二,只有 \((1,0) (2,0) (0,1) (0,2)\) 的情況。這也是遞迴的邊界,已經無法再拆分下去了。

    if (l + 1 == r) {
    	dp[l][r][1][0] = dp[l][r][2][0] = 1;
    	dp[l][r][0][1] = dp[l][r][0][2] = 1;
    	return ;
    }
    
  2. \(p[l]=r\)

    ,即 \(s[i],s[j]\) 為一組匹配的括號。

    要求 \(dp[l][r][u][v]\) ,肯定會由 \(dp[l+1][r-1][u][v]\) 得到,因為 \([l,r]\) 的最優解是建立在 \([l+1,r-1]\) 之上的。
    所以可以用四重迴圈來列舉 \(s[l],s[r],s[l+1],s[r-1]\) 染的顏色,設染的顏色分別為 \(i,j,u,v\)

    index : l  l + 1 …… r - 1  r
    color : i    u   ……   v    j
    

    枚舉出來的情況肯定有不符合條件的,所以可以根據條件進行篩選:

    1.(i + j) && !(i * j) //s[l],s[r]有且僅有一個不染色(為0)
    2.(i != u || !(i + u)) //s[l],s[l+1]要不都為0,要不都不為0且互不相等
    3.(j != v || !(j + v) //s[r],s[r-1]同理
    

    在列舉之前,顯然還要先拆分 \([l+1,r-1]\) ,也就是先求解子問題。

    dfs(l + 1, r - 1);
        for (int i = 0; i <= 2; i++) {
            for (int j = 0; j <= 2; j++) {
                for (int u = 0; u <= 2; u++) {
                    for (int v = 0; v <= 2; v++) {
                        if ((i + j) && !(i * j) && (i != u || !(i + u)) && (j != v || !(j + v))) {
                            dp[l][r][i][j] += dp[l + 1][r - 1][u][v];
                            dp[l][r][i][j] %= MOD;
                        }
                    }
                }
            }
        }
    
  3. \(s[l],s[r]\) 匹配不上。

    所以 \(s[l]\) 只能與 \(s[p[l]]\)\(s[p[l]+1]\)\(s[r]\) 進行匹配
    \(s[l],s[p[l]],s[p[l]+1],s[r]\) 染的顏色分別為 \(i,j,u,v\)

    index : l  p[l] …… p[l] + 1  r
    color : i   j   ……     u     v
    

    這裡只用去考慮 \(i,u;v,j\) 之間是否滿足,因為 \(s[l],s[p[l]]\)\(s[p[l]+1],s[r]\) 是否匹配會在遞迴求解子問題時計算,不成立的話是 \(0\),並不影響結果。

        dfs(l, p[l]);
        dfs(p[l] + 1, r);
        for (int i = 0; i <= 2; i++) {
            for (int j = 0; j <= 2; j++) {
                for (int u = 0; u <= 2; u++) {
                    for (int v = 0; v <= 2; v++) {
                        if ((j != u || !(j + u))) {
                            dp[l][r][i][v] += dp[l][p[l]][i][j] * dp[p[l] + 1][r][u][v] % MOD;
                            dp[l][r][i][v] %= MOD;
                        }
                    }
                }
            }
        }
    

Code

#include <stack>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;

const int MAXN = 705;
const int MOD = 1e9 + 7;
int n;
char s[MAXN];
int p[MAXN];
LL dp[MAXN][MAXN][3][3], ans;

stack<int> st;

void dfs(int l, int r) {
    if (l + 1 == r) {
        dp[l][r][1][0] = dp[l][r][2][0] = 1;
        dp[l][r][0][1] = dp[l][r][0][2] = 1;
        return;
    }
    if (p[l] == r) {
        dfs(l + 1, r - 1);
        for (int i = 0; i <= 2; i++) {
            for (int j = 0; j <= 2; j++) {
                for (int u = 0; u <= 2; u++) {
                    for (int v = 0; v <= 2; v++) {
                        if ((i + j) && !(i * j) && (i != u || !(i + u)) && (j != v || !(j + v))) {
                            dp[l][r][i][j] += dp[l + 1][r - 1][u][v];
                            dp[l][r][i][j] %= MOD;
                        }
                    }
                }
            }
        }
    } 
    else {
        dfs(l, p[l]);
        dfs(p[l] + 1, r);
        for (int i = 0; i <= 2; i++) {
            for (int j = 0; j <= 2; j++) {
                for (int u = 0; u <= 2; u++) {
                    for (int v = 0; v <= 2; v++) {
                        if ((j != u || !(j + u))) {
                            dp[l][r][i][v] += dp[l][p[l]][i][j] * dp[p[l] + 1][r][u][v] % MOD;
                            dp[l][r][i][v] %= MOD;
                        }
                    }
                }
            }
        }
    }
}

int main() {
    scanf("%s", s + 1);
    n = strlen(s + 1);
    for (int i = 1; i <= n; i++) {
        if (s[i] == '(')
            st.push(i);
        else
            p[st.top()] = i, st.pop(); //處理出對應下標
    }
    dfs(1, n);
    for (int i = 0; i <= 2; i++) {
        for (int j = 0; j <= 2; j++) {
            ans += dp[1][n][i][j];
            ans %= MOD;
        }
    }
    printf("%lld", ans);
    //用 long long 怕計算過程中爆掉 
    return 0;
}