P6333 [COCI2007-2008#1] ZAPIS 题解

首先观察数据范围,一眼 \(\mathcal O(n^3)\),然后再观察题目,你感觉它是个区间 dp,那么恭喜你,你的感觉是对的。

然后你直接一个区间 dp 板子拍上去,设 \(dp_{i, j}\) 表示区间 \([i, j]\) 的方案数,那么转移很显然,若 \(i,j\) 能够匹配,则可以将 \([i + 1, j - 1]\) 包起来,然后计算由两个串拼起来的。

然后你测一下样例,发现你错了。接着你模拟了一下样例,发现你算重了,例如样例 1:()()(),你输出了 2

你发现 \(\texttt{()()()} = \texttt{()} + \texttt{()()}\)\(\texttt{()()} + \texttt{()}\),于是你会统计两遍。也就是对于由两个匹配串拼起来的串,若其中的某一个串也是由某些子串拼起来的,就会算重。

于是你开始寻找原因。设 \(s_{[i,j]} = s_{[i,k_1]} + s_{[k_1+1,j]},s_{[k_1+1,j]}=s_{[k_1+1,k_2]}+s_{[k_2+1,j]}\),其中 \(s_{[i,k_1]},s_{[k_1+1,j]},s_{[k_1+1,k_2]},s_{[k_2+1,j]}\) 都是括号匹配串,那么也一定有 \(s_{[i,j]} = s_{[i,k_2]} + s_{[k_2+1,j]},s_{[i,k_2]}=s_{[i,k_1]}+s_{[k_1+1,k_2]}\),于是 \(s_{[k_1+1, k_2]}\) 就算重了。

然后你开始想办法让它只算一次。可以钦定 \(s_{[k_1+1,k_2]}\) 只在 \(s_{[k_1+1,r]}\) 中出现,那么 \(s_{[i,k]}\) 就一定不是拼接而成的括号串。于是拼接的方式形如"不拼接串+拼接串"。

现在你知道如何去重了,于是你开始修改状态。设 \(dp_{i,j,0/1}\) 表示 \([i,j]\) 不是/是拼接串的方案数。然后你将转移修改。

\[dp_{i,j,0}=check(i, j) \cdot (dp_{i+1,j-1,0}+dp_{i+1,j-1,1}) \\ dp_{i,j,1}=\sum_{k=i}^{j-1}{dp_{i,k,0} \cdot (dp_{k+1,j,0}+dp_{k+1,j,1})} \]

其中 \(check(i, j)\) 表示 \(i,j\) 是否能匹配。若 \(s_i = s_j = \texttt{?}\),则 \(check(i, j) = 3\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 505, Mod = 1e5;
int n, dp[N][N][2];
bool f;
string s;
bool check(int i, int j)
{
    return (s[i] == '(' && s[j] == ')' || s[i] == '[' && s[j] == ']' || s[i] == '{' && s[j] == '}' || (s[i] == '?' && (s[j] == ')' || s[j] == ']' || s[j] == '}') || ((s[i] == '(' || s[i] == '[' || s[i] == '{') && s[j] == '?')));
}
int add(int x, int y)
{
    if(x + y >= Mod)
        f = 1;
    return x + y >= Mod ? x + y - Mod : x + y;
}
signed main()
{
    cin >> n >> s;
    s = ' ' + s;
    for(int i = 1; i < n; i++)
        if(s[i] == s[i + 1] && s[i] == '?')
            dp[i][i + 1][0] = 3;
        else if(check(i, i + 1)) 
            dp[i][i + 1][0] = 1;
    for(int len = 4; len <= n; len += 2)
        for(int i = 1; i + len - 1 <= n; i++)
        {
            int j = i + len - 1;
            if(s[i] == s[j] && s[i] == '?')
                dp[i][j][0] = add(dp[i + 1][j - 1][0], dp[i + 1][j - 1][1]) * 3 % Mod;
            else if(check(i, j))
                dp[i][j][0] = add(dp[i + 1][j - 1][0], dp[i + 1][j - 1][1]);
            for(int k = i + 1; k < j; k += 2)
                dp[i][j][1] = add(dp[i][j][1], dp[i][k][0] * add(dp[k + 1][j][1], dp[k + 1][j][0]) % Mod);
        }
    if(f)
        return printf("%05lld", add(dp[1][n][0], dp[1][n][1])), 0;
    cout << add(dp[1][n][0], dp[1][n][1]);
    return 0;
}

P.S:这是我在考场时的心路历程,且我认为大部分人在第一次做此题时都是如此,于是我使用了第二人称。

posted @ 2025-10-13 15:59  Luckies  阅读(3)  评论(0)    收藏  举报