U230600 完美与奇偶性 题解
我们发现问题可以直接转化为选取一个区间后,区间里的数全部反转(1 -> 0, 0 -> 1
),有些数需要被反转奇数次,有些数需要被反转偶数次,从而一定地简化问题。
首先考虑爆搜,发现每次对一个区间的修改可以进行差分优化,于是考虑差分,实现单点修改,求最终状态使用前缀和(其实是异或)。
再次考虑到:条件 \(1\) 说 \(l\) 和 \(r\) 必须是 \(1 \sim 2n\) 的一个全排列,那么有这样一个性质:这个包含0, 1
的数组中,每一个元素要么是一个修改区间的左端点,要么是一个修改区间的右端点。
接着我们就可以发现:根据差分的思想,只要知道了每个元素是一次修改区间的左端点还是右端点,最后的结果就是一定的——也就是说,跟每个点具体对应的修改区间是什么没有关系。
接下来假设我们已知每个元素应该是左端点还是右端点,比如\(1, 0, 0, 1\) 中的情况就是 LLRR
(用 L
代表左,R
代表右),现在 L
和 R
可以匹配成一对有多少种匹配方案。我们可以转化为括号匹配问题,上面的 LLRR
我们转化为 (())
,但此时的括号匹配略有不同:严格的括号匹配只允许 \([1, 4], [2, 3]\) 这种情况出现,但这里我们同样也允许 \([1, 3], [2, 4]\) 这种情况(即一对括号里可以不是一个合法的括号序列)。其实这样反而比严格地简单了:匹配到一个右括号,左面有多少个未被匹配的左括号,就会对答案产生多少的贡献。根据乘法原理,这个贡献应该是乘法的。
现在问题就是如何求解每个元素是左括号还是右括号。考虑 dp。
设当前元素为 \(i\),从 \(1\) 到 \(i\) 代表左括号的元素减去代表右括号的元素一共有 \(f_{i}\) 个。
那么元素 \(a_i\) 被前 \(i - 1\) 个左括号(区间开头)翻转了 \(f_{i - 1}\) 次。(解释:\(i\) 元素左面假设有 \(x\) 个左括号,那么这 \(x\) 个左括号都有机会 \(i\) 元素带来一次翻转。哪些有机会呢?
这 \(x\) 个左括号中,对应右括号小于 \(i\) 的那 \(y\) 个,翻转区间并不包含 \(i\),完全在 \(i\) 的左方,并不会影响到元素 \(i\)。反之对应右括号大于等于 \(i\) 的 \(x - y\) 个,翻转区间包含 \(i\),可以给 \(a_i\) 翻转一次。自然 \(a_i\) 被翻转的次数是 \(x - y\),即 \(f_{i - 1}\)。)
现在假设这个元素需要被翻转奇数次(\(b_i = 1\)),而 \(f_{i - 1}\) 正好是奇数,那么此时 \(a_i\) 对应的应该是右括号,于是能保证元素继续被翻转奇数次,对应地,\(f_i \gets f_{i - 1} - 1\);如果 \(f_{i - 1}\) 是偶数,那么我们需要让元素 \(i\) 多翻转一次变成奇数,此时元素 \(i\) 应该是左括号,也就是新开一个区间。对应地,\(f_i \gets f_{i - 1} + 1\)。
假设 \(b_i = 0\),也就是需要被翻转偶数次,那么就会反过来:\(f_{i - 1}\) 是奇数的时候对应左括号;否则对应右括号。
讲法说完,接着说点事:
一、我们可以发现,统计答案时,这样一句话:
匹配到一个右括号,左面有多少个未被匹配的左括号,就会对答案产生多少的贡献。
我们发现 \(a_i\) 左方没被匹配的左括号数量恰好是 \(f_{i - 1}\),统计直接用到 \(f_{i - 1}\) 就可以(具体来说是 ans *= f[i - 1]
);
另外发现每次只会用到 \(f_i\) 和 \(f_{i - 1}\) ,因此压根用不到数组存 \(f\),采用滚动的思想维护即可。
二、注意特判几种无解情况:
- 显然 \(a_1\) 和 \(a_{2n}\) 会且只会被翻转一次,那么 \(b_1\) 和 \(b_{2n}\) 都必须是 \(1\) 才有可能有解。
- 如果按照以上的匹配方案发现最终右括号和左括号数量不等(\(f_{2n} \neq 0\)),也是无解。
- 如果 \(i\) 是一个右括号,但发现左面没有左括号也能给你用了,也是无解,不过不需要我们特殊处理,因为此时 \(f_{i - 1} = 0\),这样乘的时候答案已经是 \(0\) 了。
三、判断左括号和右括号可以更优雅。发现判断的关键是 \(b_i\) 和 \(f_{i - 1}\) 的奇偶性是否相同。维护变量 \(d\),如果 \(d = 0\) 说明 \(b_i\) 和 \(f_{i - 1}\) 相同,反之亦然。发现如果 \(b_i = b_{i - 1}\) 时,\(d\) 才会翻转(即 d ^= 1
)。为什么呢,因为从 \(i - 1\) 到 \(i\),无论如何 \(f_i\) 都会加 \(1\) 或减 \(1\),也就是说 \(f_i\) 和 \(f_{i - 1}\) 的奇偶性一定不同,同理 \(f_{i - 2}\) 和 \(f_{i - 1}\) 奇偶性不同,那么当 \(b_i = b_{i - 1}\) 时,\(b_i\) 相对于 \(f_{i - 1}\) 和 \(b_{i - 1}\) 相对于 \(f_{i - 2}\) 的奇偶性情况一定会改变,或者说翻转。反之亦然。
时间复杂度 \(\operatorname{O}(n)\)。
代码非常短。
/*
* @Author: crab-in-the-northeast
* @Date: 2022-07-21 15:11:59
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2022-07-21 16:22:24
*/
#include <bits/stdc++.h>
#define int long long
inline int read() {
int x = 0;
bool flag = true;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
flag = false;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if(flag)
return x;
return ~(x - 1);
}
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
int b[maxn * 2];
signed main() {
int T = read();
while (T--) {
int n = read();
for (int i = 1; i <= n * 2; ++i)
b[i] = read();
if (b[1] == 0 || b[n * 2] == 0) {
puts("0");
continue;
}
int cnt = 1, ans = 1;
// 这里 cnt 其实相当于 f
for (int i = 2, d = 0; i < n * 2; ++i) { // 注意不枚举到 n * 2
if (b[i] == b[i - 1])
d ^= 1;
if (d == 1)
(ans *= cnt--) %= mod;
else
++cnt;
}
if (cnt != 1) { // 这里的 cnt 是 f[2 * n - 1]
puts("0");
continue;
}
printf("%lld\n", ans);
}
return 0;
}
标签:匹配,自做,元素,奇偶性,括号,区间,自切,翻转 来源: https://www.cnblogs.com/crab-in-the-northeast/p/luogu-u230600.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。