完美与奇偶性 自做自切
U230600 完美与奇偶性 题解
我们发现问题可以直接转化为选取一个区间后,区间里的数全部反转(1 -> 0, 0 -> 1
),有些数需要被反转奇数次,有些数需要被反转偶数次,从而一定地简化问题。
首先考虑爆搜,发现每次对一个区间的修改可以进行差分优化,于是考虑差分,实现单点修改,求最终状态使用前缀和(其实是异或)。
再次考虑到:条件 说 和 必须是 的一个全排列,那么有这样一个性质:这个包含0, 1
的数组中,每一个元素要么是一个修改区间的左端点,要么是一个修改区间的右端点。
接着我们就可以发现:根据差分的思想,只要知道了每个元素是一次修改区间的左端点还是右端点,最后的结果就是一定的——也就是说,跟每个点具体对应的修改区间是什么没有关系。
接下来假设我们已知每个元素应该是左端点还是右端点,比如 中的情况就是 LLRR
(用 L
代表左,R
代表右),现在 L
和 R
可以匹配成一对有多少种匹配方案。我们可以转化为括号匹配问题,上面的 LLRR
我们转化为 (())
,但此时的括号匹配略有不同:严格的括号匹配只允许 这种情况出现,但这里我们同样也允许 这种情况(即一对括号里可以不是一个合法的括号序列)。其实这样反而比严格地简单了:匹配到一个右括号,左面有多少个未被匹配的左括号,就会对答案产生多少的贡献。根据乘法原理,这个贡献应该是乘法的。
现在问题就是如何求解每个元素是左括号还是右括号。考虑 dp。
设当前元素为 ,从 到 代表左括号的元素减去代表右括号的元素一共有 个。
那么元素 被前 个左括号(区间开头)翻转了 次。(解释: 元素左面假设有 个左括号,那么这 个左括号都有机会 元素带来一次翻转。哪些有机会呢?
这 个左括号中,对应右括号小于 的那 个,翻转区间并不包含 ,完全在 的左方,并不会影响到元素 。反之对应右括号大于等于 的 个,翻转区间包含 ,可以给 翻转一次。自然 被翻转的次数是 ,即 。)
现在假设这个元素需要被翻转奇数次(),而 正好是奇数,那么此时 对应的应该是右括号,于是能保证元素继续被翻转奇数次,对应地,;如果 是偶数,那么我们需要让元素 多翻转一次变成奇数,此时元素 应该是左括号,也就是新开一个区间。对应地,。
假设 ,也就是需要被翻转偶数次,那么就会反过来: 是奇数的时候对应左括号;否则对应右括号。
讲法说完,接着说点事:
一、我们可以发现,统计答案时,这样一句话:
匹配到一个右括号,左面有多少个未被匹配的左括号,就会对答案产生多少的贡献。
我们发现 左方没被匹配的左括号数量恰好是 ,统计直接用到 就可以(具体来说是 ans *= f[i - 1]
);
另外发现每次只会用到 和 ,因此压根用不到数组存 ,采用滚动的思想维护即可。
二、注意特判几种无解情况:
- 显然 和 会且只会被翻转一次,那么 和 都必须是 才有可能有解。
- 如果按照以上的匹配方案发现最终右括号和左括号数量不等(),也是无解。
- 如果 是一个右括号,但发现左面没有左括号也能给你用了,也是无解,不过不需要我们特殊处理,因为此时 ,这样乘的时候答案已经是 了。
三、判断左括号和右括号可以更优雅。发现判断的关键是 和 的奇偶性是否相同。维护变量 ,如果 说明 和 相同,反之亦然。发现如果 时, 才会翻转(即 d ^= 1
)。为什么呢,因为从 到 ,无论如何 都会加 或减 ,也就是说 和 的奇偶性一定不同,同理 和 奇偶性不同,那么当 时, 相对于 和 相对于 的奇偶性情况一定会改变,或者说翻转。反之亦然。
时间复杂度 。
代码非常短。
/*
* @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;
}