题解:CF1031E Triple Flips
题意简述
有一个长度为
思路
观察限制
于是显然想到把原序列分成三个三个一组,尝试用
但经过简单尝试我们发现这是不可能的,因为除了 111 和 000,别的所有情况都不能在一次操作之内消除。
于是我们想到把长度翻倍,每
我们钦定第一位为
:::info[分类讨论]
其中 s 表示区间 Flip(x, y) 表示操作
大家可以手玩几组理解一下。
if (s == "100000") Flip(l, l + 2), Flip(l + 2, l + 4);
if (s == "100001") Flip(l, l + 5);
if (s == "100010") Flip(l, l + 4);
if (s == "100011") Flip(l, l + 3), Flip(l + 3, l + 4);
if (s == "100100") Flip(l, l + 3);
if (s == "100101") Flip(l, l + 3), Flip(l + 5, l + 6);
if (s == "100110") Flip(l, l + 5), Flip(l + 3, l + 4);
if (s == "100111") Flip(l, l + 3), Flip(l + 4, l + 5);
if (s == "101000") Flip(l, l + 2), Flip(l + 4, l + 6);
if (s == "101001") Flip(l, l + 2), Flip(l + 4, l + 5);
if (s == "101010") Flip(l, l + 2);
if (s == "101011") Flip(l, l + 2), Flip(l + 5, l + 6);
if (s == "101100") Flip(l, l + 4), Flip(l + 2, l + 3);
if (s == "101101") Flip(l, l + 2), Flip(l + 3, l + 4);
if (s == "101110") Flip(l, l + 2), Flip(l + 3, l + 6);
if (s == "101111") Flip(l, l + 2), Flip(l + 3, l + 5);
if (s == "110000") Flip(l, l + 4), Flip(l + 1, l + 4);
if (s == "110001") Flip(l, l + 3), Flip(l + 1, l + 3);
if (s == "110010") Flip(l, l + 1), Flip(l + 2, l + 4);
if (s == "110011") Flip(l, l + 4), Flip(l + 1, l + 5);
if (s == "110100") Flip(l, l + 5), Flip(l + 1, l + 3);
if (s == "110101") Flip(l, l + 3), Flip(l + 1, l + 5);
if (s == "110110") Flip(l, l + 1), Flip(l + 2, l + 3);
if (s == "110111") Flip(l, l + 4), Flip(l + 1, l + 3);
if (s == "111000") Flip(l, l + 1);
if (s == "111001") Flip(l, l + 1), Flip(l + 5, l + 6);
if (s == "111010") Flip(l, l + 1), Flip(l + 4, l + 6);
if (s == "111011") Flip(l, l + 1), Flip(l + 4, l + 5);
if (s == "111100") Flip(l, l + 1), Flip(l + 3, l + 6);
if (s == "111101") Flip(l, l + 1), Flip(l + 3, l + 5);
if (s == "111110") Flip(l, l + 4), Flip(l + 1, l + 2);
if (s == "111111") Flip(l, l + 1), Flip(l + 3, l + 4);
:::
但是这样会需要大量的冗余空间,如情况 110100,清除方法是:
- 翻转第
1 、6 、11 位; - 翻转第
2 、4 、6 位。
最多触及后
到这里,显然的想法是对长度
于是,除非你想分讨
于是我们整个题目的思路就出来了:
- 对于所有的
1 \le i \le \max(0, n - 10) 的位置,如果它是1 ,就把后面的6 位全部清除。 - 对于最后的
\min(n, 10) 位,我们直接暴力搜索,找出最优解。
复杂度分析
前面的分讨复杂度
前面的操作平均每次清除
代码实现
实际实现时可以把
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 10;
bitset<N> a;
struct Ans {
int a, b, c;
};
vector<Ans> ans;
pair<pair<int, int>, pair<int, int>> opt[32] = {
{{0, 2}, {2, 4}}, {{0, 5}, {-1, -1}}, {{0, 4}, {-1, -1}},
{{0, 3}, {3, 4}}, {{0, 3}, {-1, -1}}, {{0, 3}, {5, 6}},
{{0, 5}, {3, 4}}, {{0, 3}, {4, 5}}, {{0, 2}, {4, 6}},
{{0, 2}, {4, 5}}, {{0, 2}, {-1, -1}}, {{0, 2}, {5, 6}},
{{0, 4}, {2, 3}}, {{0, 2}, {3, 4}}, {{0, 2}, {3, 6}},
{{0, 2}, {3, 5}}, {{0, 4}, {1, 4}}, {{0, 3}, {1, 3}},
{{0, 1}, {2, 4}}, {{0, 4}, {1, 5}}, {{0, 5}, {1, 3}},
{{0, 3}, {1, 5}}, {{0, 1}, {2, 3}}, {{0, 4}, {1, 3}},
{{0, 1}, {-1, -1}}, {{0, 1}, {5, 6}}, {{0, 1}, {4, 6}},
{{0, 1}, {4, 5}}, {{0, 1}, {3, 6}}, {{0, 1}, {3, 5}},
{{0, 4}, {1, 2}}, {{0, 1}, {3, 4}}};
int pre[N];
Ans pre_op[N];
int Mask(int x, int len) {
int ans = 0;
for (int i = x; i < x + len; ++i) ans = ans << 1 | a[i];
return ans;
}
void Flip(int x, int y) {
int z = y * 2 - x;
ans.push_back({x, y, z});
a[x] = a[x] ^ 1, a[y] = a[y] ^ 1, a[z] = a[z] ^ 1;
}
void Clear(int l) {
int mask = Mask(l + 1, 5);
Flip(l + opt[mask].first.first, l + opt[mask].first.second);
if (~opt[mask].second.first && ~opt[mask].second.second) {
Flip(l + opt[mask].second.first, l + opt[mask].second.second);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for (int i = 1, x; i <= n; ++i) cin >> x, a[i] = x;
for (int i = 1; i <= n - 10; ++i) {
if (a[i]) Clear(i);
}
int l = max(1, n - 9), len = n - l + 1, s = 0;
for (int i = n; i >= l; --i) s = s << 1 | a[i];
memset(pre, -1, sizeof(pre));
queue<int> q;
q.push(s);
pre[s] = -2;
while (!q.empty()) {
int x = q.front();
q.pop();
if (!x) break;
for (int i = l; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
int k = j * 2 - i;
if (k < l || k > n) continue;
int nxt = x ^ ((1 << i - l) | (1 << j - l) | (1 << k - l));
if (~pre[nxt]) continue;
pre[nxt] = x;
pre_op[nxt] = {i, j, k};
q.push(nxt);
}
}
}
if (pre[0] == -1) return cout << "NO\n", 0;
int x = 0, now_len = ans.size();
while (pre[x] != -2) {
ans.push_back(pre_op[x]);
x = pre[x];
}
reverse(ans.begin() + now_len, ans.end());
cout << "YES\n" << ans.size() << '\n';
for (auto x : ans) cout << x.a << ' ' << x.b << ' ' << x.c << '\n';
}
:::
后记
其实对于所有的
当
n\ge 8 时,任意0/1 数组都可以通过若干次 Triple Flips 变为全0 。
把每个位置看成
其中
要证明任意数组都能变成全零,只需证明这些操作向量张成整个空间
反设它们不能张成整个空间。则存在非零向量
与所有操作向量正交,即对任意等差数列
先取公差为
于是
进一步,
所以序列
记
由
因此
因为
由 (1) 对
利用周期
于是
因为
同理,对
利用周期
于是
从而
因此
所以所有
| 因此操作向量张成整个 |
|---|