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;
}