2025.05.13 CW 模拟赛 A. 灯
A. 灯
题目描述
有 \(n\) 个灯排成一排,有的灯是开着的,有的灯是关着的。第 \(i\) 盏灯的开关状态是 \(a_i\)(1 表示开,0 表示关)。现在希望让第 \(i\) 盏灯的开关状态变成 \(b_i\)。
有 \(m\) 个开关,第 \(i\) 个可以翻转 \([l_i, r_i]\) 中的所有灯的开关状态(即 1 变成 0,0 变成 1)。每种开关最多只能按一次。
你需要求出有多少种按开关的方案,使得所有灯都能够到达想要的状态。由于方案数可能很大,只需要输出答案对 \(10^9 + 7\) 取模的结果。特别的,如果存在至少一组方案,你还需要给出一组构造。
思路
发现不好 \(\rm{DP}\).
我们将 \(a\) 数组异或上 \(b\) 数组然后差分一下, 题意就转化为: 每次修改两个端点, 求将数组中所有数全部变为 \(0\) 的方案数.
对于所有的 \(l_i, r_i\), 操作之间可能会互相影响, 我们把它们放在图上. 具体来说, 我们将两个端点 \(l, r + 1\) 进行连边 \((\)因为是差分数组\()\), 然后会形成许多连通块. 考虑什么时候无解. 由于翻转一条边后连通块中 \(1\) 的个数的奇偶性不变, 所以如果某一个连通块中 \(1\) 的个数为奇数一定无解.
对于每一个连通块, 我们找出其一个生成树, 可以发现仅翻转这棵树上的某些边就能够合法, 其余非树边可选可不选, 最后的答案就要乘上 \(2^{\text{非树边数量}}\).
至于构造方案, 我们在 \(\rm{DFS}\) 寻找生成树回溯的时候进行处理, 如果该点的儿子为 \(1\), 那就不得不对这条边进行操作.
void calculate() {
int tr = 0;
auto dfs = [&](auto&& self, int u) -> int {
int res = 0;
vis[u] = true;
for (auto [v, id] : e[u]) {
if (vis[v]) {
continue;
}
++tr, res += self(self, v);
if (w[v]) {
w[v] ^= 1, w[u] ^= 1;
ans[id] = 1;
}
}
return res + a[u];
};
for (int i = 1; i <= n; ++i) {
if (vis[i]) {
continue;
}
if (dfs(dfs, i) & 1) {
puts("0\nNO");
return;
}
}
printf("%d\nYES\n", qpow(2, m - tr));
for (int i = 1; i <= m; ++i) {
printf("%d", ans[i]);
}
}
后话
对于相互影响的约束, 可以转化为图上问题进行处理.

浙公网安备 33010602011771号