Codeforces Round 106 D. Coloring Brackets 【区间DP + 记忆化搜索实现】

D. Coloring Brackets

1
约定 ∣ S ∣ ≤ 700 |S| \leq 700 S700

题意

给定一个正则括号序列 s s s,我们需要求出合法的染色方案数。合法的条件为:

  1. 每个符号要么不染色,要么染红色,要么染蓝色
  2. 对于每对配对的括号,它们中恰好有一个染色了,另外一个每染色
  3. 相邻的染色的字符颜色不能相同(没有染色允许相邻)
思路

可以发现每个左括号配对的右括号都是唯一的,我们可以使用栈来预处理出来,那么原本的 s s s 就被分成了若干个首尾配对连续正则序列,例如 s = ( ( ( ) ) ( ) ) ( ) s = ( (())())() s=((())())() 可以分成: ( ( ( ) ) ( ) ) ((())()) ((())()) ( ) () () 两个连续正则序列

这里是一个子状态,我们可以使用区间 D P DP DP 来计算。假设 d p [ l ] [ r ] [ c 1 ] [ c 2 ] dp[l][r][c1][c2] dp[l][r][c1][c2] 表示 l l l 的颜色为 c 1 c1 c1 r r r 的颜色为 c 2 c2 c2 时,区间 [ l , r ] [l,r] [l,r] 合法的方案数。

由于 s s s 本身是一个合法正则序列,我们先在外面枚举 c 1 c1 c1 c 2 c2 c2,进入 d f s dfs dfs 后,再根据它首尾是否配对,来分情况从不同的子状态转移过来。

  • 如果 l 、 r l、r lr 配对,也就是 l l l 位置的左括号和 r r r 位置的右括号配对,那么可以直接从子状态 d p [ l + 1 ] [ r − 1 ] dp[l + 1][r - 1] dp[l+1][r1] 转移,注意要枚举子状态两端的颜色,并判断相邻颜色是否合法
  • 如果 l 、 r l、r lr 不配对,我们就要将区间 [ l , r ] [l,r] [l,r] 分裂成两个子区间 [ l , n x t [ l ] ] [l, nxt[l]] [l,nxt[l]] [ n x t [ l ] + 1 , r ] [nxt[l] + 1, r] [nxt[l]+1,r],枚举 n x t [ l ] nxt[l] nxt[l] n x t [ l ] + 1 nxt[l] + 1 nxt[l]+1 位置的颜色,判断是否合法再转移

由于这道题每个 l l l 都有唯一的一个配对位置 n x t [ l ] nxt[l] nxt[l],所以我们在做区间 D P DP DP 时就省去了枚举区间分裂点这一层操作,加上外层的 c 1 、 c 2 c1、c2 c1c2 颜色枚举,复杂度大约为 O ( 9 ⋅ n 2 ) O(9 \cdot n ^ 2) O(9n2)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

template<class T>
constexpr T power(T a, ll b){
    T res = 1;
    while(b){
        if(b&1) res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}

constexpr ll mul(ll a,ll b,ll mod){ //快速乘,避免两个long long相乘取模溢出
    ll res = a * b - ll(1.L * a * b / mod) * mod;
    res %= mod;
    if(res < 0) res += mod; //误差
    return res;
}

template<ll P>
struct MLL{
    ll x;
    constexpr MLL() = default;
    constexpr MLL(ll x) : x(norm(x % getMod())) {}

    static ll Mod;
    constexpr static ll getMod(){
       if(P > 0) return P;
       return Mod;
    }

    constexpr static void setMod(int _Mod){
       Mod = _Mod;
    }
    constexpr ll norm(ll x) const{
       if(x < 0){
           x += getMod();
       }
       if(x >= getMod()){
           x -= getMod();
       }
       return x;
    }
    constexpr ll val() const{
       return x;
    }
    explicit constexpr operator ll() const{ 
       return x; //将结构体显示转换为ll类型: ll res = static_cast<ll>(OBJ)
    }
    constexpr MLL operator -() const{ //负号,等价于加上Mod
       MLL res;
       res.x = norm(getMod() - x);
       return res;
    }
    constexpr MLL inv() const{
       assert(x != 0);
       return power(*this, getMod() - 2); //用费马小定理求逆
    }
    constexpr MLL& operator *= (MLL rhs) & { //& 表示“this”指针不能指向一个临时对象或const对象
       x = mul(x, rhs.x, getMod()); //该函数只能被一个左值调用
       return *this;
    }
    constexpr MLL& operator += (MLL rhs) & {
       x = norm(x + rhs.x);
       return *this;
    }
    constexpr MLL& operator -= (MLL rhs) & {
       x = norm(x - rhs.x);
       return *this;
    }
    constexpr MLL& operator /= (MLL rhs) & {
       return *this *= rhs.inv();
    }
    friend constexpr MLL operator * (MLL lhs, MLL rhs){
       MLL res = lhs;
       res *= rhs;
       return res;
    }
    friend constexpr MLL operator + (MLL lhs, MLL rhs){
       MLL res = lhs;
       res += rhs;
       return res;
    }
    friend constexpr MLL operator - (MLL lhs, MLL rhs){
       MLL res = lhs;
       res -= rhs;
       return res;
    }
    friend constexpr MLL operator / (MLL lhs, MLL rhs){
       MLL res = lhs;
       res /= rhs;
       return res;
    }
    friend constexpr std::istream& operator >> (std::istream& is, MLL& a){
       ll v;
       is >> v;
       a = MLL(v);
       return is;
    }
    friend constexpr std::ostream& operator << (std::ostream& os, MLL& a){
       return os << a.val();
    }
    friend constexpr bool operator == (MLL lhs, MLL rhs){
       return lhs.val() == rhs.val();
    }
    friend constexpr bool operator != (MLL lhs, MLL rhs){
       return lhs.val() != rhs.val();
    }
};

const ll mod = 1e9 + 7;
using Z = MLL<mod>;

const int N = 750;

Z dp[N][N][4][4];

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    std::string s;
    std::cin >> s;
    int n = s.size();
    s = '0' + s;

    std::vector<int> nxt(n + 5, 0);

    std::stack<int> st;
    fore(i, 1, n + 1)
        if(s[i] == '(') st.push(i);
        else{
            nxt[st.top()] = i;
            st.pop();
        }

    auto check1 = [](int c1, int c2) -> bool { //检查相邻染色是否合法
        return !c1 || !c2 || c1 != c2;
    };

    auto check2 = [](int c1, int c2) -> bool { //检查首尾括号配对染色是否合法
        return (!c1 || !c2) && c1 ^ c2;
    };

    auto dfs = [&](auto self, int l, int r, int c1, int c2) -> Z {
        if(l > r)   return 0;
        if(dp[l][r][c1][c2].x) return dp[l][r][c1][c2];
        if(l + 1 == r) return dp[l][r][c1][c2] = check2(c1, c2);
        Z ans = 0;

        if(nxt[l] == r){ //l r 配对,直接转移
            if(!check2(c1, c2)) return 0;
            fore(i, 0, 3)
                fore(j, 0, 3){
                    if(!check1(c1, i) || !check1(j, c2)) continue;
                    ans += self(self, l + 1, r - 1, i, j);
                }
        }
        else{ //l r 不配对,继续划分字串
            fore(i, 0, 3)
                fore(j, 0, 3){
                    if(!check1(i, j)) continue;
                    ans += self(self, l, nxt[l], c1, i) * self(self, nxt[l] + 1, r, j, c2);
                }
        }

        return dp[l][r][c1][c2] = ans;
    };

    Z ans = 0;
    fore(i, 0, 3)
        fore(j, 0, 3)
            ans += dfs(dfs, 1, n, i, j);
    
    std::cout << ans;

    return 0;
}
  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值