题解:「MCOI-03」括号
一个非常显然的事实是,对于一个区间 \(K = 0\) 的值就是没有匹配的括号数。
首先考虑 \(K = 1\) 怎么做。
考虑每个括号的贡献,按照处理括号的套路,拿个栈从左到右未匹配的 (
,那么每个 )
的合法区间的左端点必定位于这个括号的位置及这个位置的左边和栈中上个未匹配的 (
的右边,右端点当然位于这个括号右边的任意位置,)
同理,然后就可以拿到 22
分了。
inline void solve() {
Mint res(0);
forn (i, 1, n) {
if (s[i] == '(') stk[++top] = i;
else {
res += Mint(i - stk[top]) * Mint(n - i + 1);
if (top) top -- ;
}
}
top = 0; stk[0] = n + 1;
form (i, n, 1) {
if (s[i] == ')') stk[++top] = i;
else {
res += Mint(stk[top] - i) * Mint(i);
if (top) top -- ;
}
}
Wtn(res.res, '\n');
}
然后考虑 \(K > 1\) 的情况,你考虑打一个暴力,然后发现暴力复杂度爆炸了,比 \(\mathcal O (n ^ 3)\) 都大,很是自闭。
这个暴力先计算好了 \(K = 1\) 时每个区间的答案,然后再暴力地把每个区间向包含它的所有区间造成一次贡献,然后复杂度是 \(\mathcal O (上天)\) 。
然后考虑每个区间的贡献如何快速求,设现在这个区间的答案是 \(f_k(l, r)\) ,那么这个东西对于最后答案的贡献就是将会变成的左端点贡献个数乘上将会变成的右端点的贡献个数,这里的将会的贡献,指的是将会包含这个区间的东西将会计算的贡献的系数,再乘上这个 \(f_k(l, r)\) ,所以左右端点的贡献是裂开的。
那么先对左端点计算,右端点就同理了。
进行 \(K = 1\) 的操作之后,对于一个左端点 \(l\) ,假设只有由这个左端点构成的区间,那么:
设 \(f_k(l)\) 指这个 \(l\) 在 \(K = k\) 时候最后的贡献,\(g_k(p)\) 表示 \(p\) 这个节点在 \(K = k\) 的时候的权值,然后有递推式:
这个东西肯定是上天的,然后发现这个 \(f\) 除了统计个和没有任何用处,所以下边只考虑 \(g\) 。
将这个从 \(l\) 到 \(1\) 的端点看成从 \(0\) 到 \(l - 1\) 的端点。
那么一开始有 \(g_1(0) = 1\) ,然后 \(g_k\) 就是 \(g_1\) 的 \(k - 1\) 维前缀和。
然后这个东西非常经典,把 \(g_2(i)\) 看成 \({i\choose 0}\) ,那么 \(g_k(i)\) 就是 \({i + k - 2\choose k - 2}\) 。
然后这个时候就可以 \(\mathcal O (n ^ 2)\) 了,然后你把 \(K = 1\) 的所有点的贡献离线下来,就变成一个最基础的二维数点,连 ds
都不用就做完了。
时间复杂度 \(\mathcal O (n\log n)\) 。
Mint fac[N << 1], ifac[N << 1];
inline void table(int lim) {
fac[0] = Mint(1);
forn (i, 1, lim) fac[i] = fac[i - 1] * Mint(i);
ifac[lim] = q_pow(fac[lim]);
form (i, lim - 1, 0) ifac[i] = ifac[i + 1] * Mint(i + 1);
}
inline Mint C(int n, int r) {
if (n < 0 || r < 0 || n < r) return Mint(0);
return fac[n] * ifac[r] * ifac[n - r];
}
struct node {
int pos, l, r;
node() {}
node(int _p, int _l, int _r) : pos(_p), l(_l), r(_r) {}
inline friend bool operator < (const node& A, const node& B) {
return A.pos < B.pos;
}
} Q[N << 1]; int m;
inline Mint Fl(int l) {return C(l + k - 2, k - 1); }
inline Mint Fr(int r) {return C(n - r + k - 1, k - 1); }
Mint sum[N];
inline void solve() {
m = 0, table(n + k);
forn (i, 1, n) {
sum[i] = sum[i - 1] + Fl(i);
if (s[i] == '(') stk[++top] = i;
else {
Q[++m] = node(i, stk[top] + 1, i);
if (top) top -- ;
}
}
top = 0; stk[0] = n + 1;
form (i, n, 1) {
if (s[i] == ')') stk[++top] = i;
else {
Q[++m] = node(i, 1, i), Q[++m] = node(stk[top], -1, -i);
if (top) top -- ;
}
}
sort (Q + 1, Q + m + 1);
// forn (i, 1, m) Wtn(Q[i].pos, ":: [", Q[i].l, ", ", Q[i].r, "]", " \n"[i == m]);
int now = 1; Mint lst(0), res(0);
forn (i, 1, n) {
while (now <= m && Q[now].pos <= i) {
if (Q[now].l > 0) lst += sum[Q[now].r] - sum[Q[now].l - 1], ++now;
else lst -= sum[-Q[now].r] - sum[-Q[now].l - 1], ++now;
}
res += Fr(i) * lst;
}
Wtn(res.res, '\n');
}
看了题解后发现自己变成了 **
,对于 (
和 )
的贡献完全可以分开算,所以完全不用离线,直接做就好了,时间复杂度 \(\mathcal O(n)\) 。