P7044 「MCOI-03」括号

P7044 「MCOI-03」括号

题目大意

定义一个括号串的 \(0\) 级偏值为将该括号串修改为 合法括号串 需要的最小操作数。一次操作你可以在一个位置 添加 一个括号或者 删除 一个位置的括号。

定义一个括号串的 \(i\ (i>0)\) 级偏值为该串 所有子串\(i-1\) 级偏值之和。

现在你需要求出一个长度为 \(N\) 的括号串 \(S\)\(K\) 级偏值。答案可能很大,输出对 \(998244353\) 取模后的结果。

分析

奇妙的题目。

我们推导一下式子,我们先考虑K=1的情况。

K=1

对于这种情况,我们只需要考虑对于所有子串,使其变为合法括号序列的代价。

这点很好推导,一个括号串的 0 级偏值就是进行括号匹配后还未匹配的左括号和右括号数之和(因为中间括号尽可能配对一定是最优的)。

因此,设f(l,r)表示区间[l,r]变为合法序列的代价,式子即为

\[\sum_{1\leq l \leq r \leq n}f(l,r) \]

k>1

此时,我们需要考虑对于,不断上传的影响的k个区间,我们假设此时我们考虑的是区间[l,r],则为求其在k次中所贡献的值影响的区间数量。

我们先把式子放在这里。

\[\sum_{1 \leq l \leq r \leq n}C(l+k-2,k-1)C(n-r+k-1,k-1)f(l,r) \]

我们来解释一下这个式子,其实也比较好理解,就是对于区间[l,r],k次传递中会被其影响到的区间数量即为,在\(\leq l\)的区间中选择k-1个点,为左端点,再从\(\geq r\)的区间选择k-1个点,这些点可以重复。接下来的,对于从左右两边选择的k-1个点,从左右两端匹配,即为\([l_i,r_{k-i}]\)再加上初始的[l,r]这样就能得到k个区间,因为一一包括的关系,所以[l,r]k-1个区间都有贡献。贡献即为f(l,r)

我们再转化一下式子。

\[\sum_{l=1}^{n}C(l+k-2,k-1)\sum_{r=l}^{n}C(n-r+k-1,k-1)f(l,r) \]

然后\(\sum_{r=l}^{n}C(n-r+k-1,k-1)f(l,r)\),我们可以用后缀和维护,有一些细节我会在代码中解释。

#include<bits/stdc++.h>

using namespace std;

const int N = 2e6 + 10,mod = 998244353;

template<int T>
struct ModInt {
    const static int mod = T;
    int x;
    ModInt(int x = 0) : x(x % mod) {}
    int val() { return x; }
    ModInt operator + (const ModInt &a) const { int x0 = x + a.x; if (x0 >= mod) x0 -= mod; if (x0 < 0) x0 += mod; return ModInt(x0); }
    ModInt operator - (const ModInt &a) const { int x0 = x - a.x; if (x0 >= mod) x0 -= mod; if (x0 < 0) x0 += mod; return ModInt(x0); }
    ModInt operator * (const ModInt &a) const { return ModInt(1LL * x * a.x % mod); }
    ModInt operator / (const ModInt &a) const { return *this * a.inv(); }
    void operator += (const ModInt &a) { x += a.x; if (x >= mod) x -= mod; if (x < 0) x += mod;}
    void operator -= (const ModInt &a) { x -= a.x; if (x < 0) x += mod; if (x >= mod) x -= mod;}
    void operator *= (const ModInt &a) { x = 1LL * x * a.x % mod; }
    void operator /= (const ModInt &a) { *this = *this / a; }
    friend ostream &operator<<(ostream &os, const ModInt &a) { return os << a.x;}
    
    ModInt pow(int n) const {
        ModInt res(1), mul(x);
        while(n){
            if (n & 1) res *= mul;
            mul *= mul;
            n >>= 1;
        }
        return res;
    }
    
    ModInt inv() const {
        int a = x, b = mod, u = 1, v = 0;
        while (b) {
            int t = a / b;
            a -= t * b; swap(a, b);
            u -= t * v; swap(u, v);
        }
        if (u < 0) u += mod;
        return u;
    }
    
};
typedef ModInt<mod> mint;

mint fact[N],infact[N],r[N];
int n,k;
char s[N];
int sta[N],st,rp[N],l;

void init()
{
    fact[0] = infact[0] = 1;
    for(int i=1;i<N;i++) fact[i] = fact[i-1]*mint(i);
    infact[N-1] = fact[N-1].inv();
    for(int i=N-2;i;i--) infact[i] = infact[i+1]*mint(i+1);
}

mint C(int a,int b)
{
    return fact[a]*infact[a-b]*infact[b];
}

int main()
{
    cin>>n>>k;
    cin>>s+1;
    init();
    for(int i=1;i<=n;i++) r[i] = C(n-i+k-1,k-1);
    mint lans = 0;
    for(int i=1;i<=n;i++)
    {
        if(s[i]=='(') sta[++st] = i;//将左括号入栈
        else if(st) rp[sta[st--]] = i;//若出现匹配,则记录一下当前栈内对应的括号对应的括号的编号
        else ++l;//否做[1,i]区间多一个右括号
        lans += r[i]*mint(st+l);//先求出l=1时需要乘的值
    }
    for(int i=n;i;i--) r[i] += r[i+1];//算一下后缀和
    mint ans = 0;
    for(int i=1;i<=n;i++)
    {
        ans += C(i+k-2,k-1)*lans;
        lans -= r[i];//每次先将该点的对后面所有区间贡献减去
        if(rp[i]) lans += mint(2)*r[rp[i]];
        //若是该点没有贡献,则将其对应的位置后面都算上一个新的贡献,同时由于(l,r)中间并没有新增加这个贡献,因此,我们需要在区间(l,r)减去这个影响
    }
    cout<<ans<<'\n';
    return 0;
}
posted @ 2022-09-07 23:33  艾特玖  阅读(53)  评论(0)    收藏  举报