CF79D
题意:初始有 \(n\) 个 \(0\),给定一个序列 \(a\),每次可以选择一个长度为某个 \(a_i\) 的区间,将其全部取反。再给定一个序列 \(x\),要求最后的状态是只有 \(x\) 中的位置是 \(1\)。问最小步数/判断无解。
范围:\(n\le 10000,|a|=l\le 100,|x|=k\le 10\)。
关于区间取反的,考虑差分。根据 \(x\) 求出最终需要的差分序列。(注意要包括 \(n+1\) 位置)
然后区间取反就变成两个单点取反。
要求从全 \(0\) 开始,每次可以把两个隔了 \(a_i\) 的位置取反,变成目标。
发现这个过程具有可逆性,所以可以看作从目标状态变成全0。
再看一看,发现一定不会把两个0变成两个1,这样一定不优;一个 0 一个 1 取反可以视作交换。
所以每次操作实际上是选定两个 1,经过若干次交换来调整位置,然后把它们消掉。(注意包括 \(n+1\))
注意到 \(k\le 10\),所以差分序列的 \(1\) 个数 \(\le 20\),考虑状压。
\(dp[S]\) 表示 \(S\) 中的 \(1\) 还没有被消除所需的最小次数。(\(S\) 是一个长度 \(\le 20\) 的二进制数,第 \(i\) 位代表原本差分序列的第 \(i\) 个 \(1\))
初值 \(dp[0]=0,dp[S]=+\infty\)。
递推:枚举 \(S\) 中两个还是 \(1\) 的位置 \(i,j\),若经过若干次调整位置,将它们消除的代价是 \(f[i][j]\),则 \(dp[S]\leftarrow \min(dp[S],dp[S-2^i-2^j]+f[i][j])\)。
怎么求 \(f[i][j]\)?这可以用 BFS。具体而言,对于每个 \(a_i\),我们把所有 \(pos\) 和 \(pos+a_i\) 连边。(要求 \(pos+a_i\le n+1\))
然后从差分序列中每个 \(1\) 的位置分别作为起点,跑 BFS,就求出了这个 \(1\) 与其他 \(1\) 消除的代价。
#include <bits/stdc++.h>
using namespace std;
const int N = (1 << 20) + 5;
int n, k, l, a[105];
int x[10005];
int rev[10005];
vector<int> v;
int f[25][25];
int dp[N];
vector<int> e[10005];
int dist[10005];
void bfs(int s) {
// if (s == 2)
// cout << s << endl;
queue<int> q;
memset(dist, 0x3f, sizeof dist);
dist[s] = 0;
q.push(s);
while (!q.empty()) {
int h = q.front();
q.pop();
// if (s == 2)
// cout << h << endl;
for (auto i: e[h])
if (dist[i] == 0x3f3f3f3f) {
dist[i] = dist[h] + 1;
q.push(i);
}
}
}
void init() { //预处理
for (int i = 0; i < v.size(); i++) {
bfs(v[i]);
for (int j = 0; j < v.size(); j++) {
f[rev[v[i]]][rev[v[j]]] = dist[v[j]];
// printf("%d=>%d:%d\n", rev[i], rev[v[j]], dist[v[j]]);
}
}
}
int main() {
cin >> n >> k >> l;
for (int i = 1, tmp; i <= k; i++) {
cin >> tmp;
x[tmp] ^= 1;
x[tmp + 1] ^= 1;
}
for (int i = 1; i <= l; i++) {
cin >> a[i];
for (int j = 1; j + a[i] <= n + 1; j++) {
e[j].push_back(j + a[i]);
e[j + a[i]].push_back(j);
}
}
// for (int i = 1; i <= n + 1; i++)
// cout << x[i] << ' ';
// puts("");
//要把x数组全部消成0
for (int i = 1; i <= n + 1; i++)
if (x[i] == 1) {
rev[i] = v.size();
v.push_back(i);
}
init();
// cout << f[0][1] << endl;
memset(dp, 0x3f, sizeof dp);
dp[0] = 0;
for (int S = 1; S < (1 << v.size()); S++) {
for (int i = 0; i < v.size(); i++)
if (((S >> i) & 1) == 1) {
for (int j = i + 1; j < v.size(); j++)
if (((S >> j) & 1) == 1)
dp[S] = min(dp[S], dp[S - (1 << i) - (1 << j)] + f[i][j]);
}
}
if (dp[(1 << v.size()) - 1] == 0x3f3f3f3f)
cout << -1 << endl;
else
cout << dp[(1 << v.size()) - 1] << endl;
return 0;
}
//1 2 10000 10001