题意简述
有一个长度为n的01序列,有k个1
每次可以将给定长度的子串取反,即0->1, 1->0
求最少几次可以将整个序列都变为0
题解思路
差分+状压DP
首先将原序列异或差分
每消去一对1就是将1移到一起
先用bfs预处理出每两个1移到一起的最小步数
然后用状压DP来求总的最小步数
代码
#include <queue>
#include <cstdio>
const int INF = 0x3f3f3f3f;
int n, k, m, x, xx, cnt, C, X;
int b[50000], c[50000], s[50000];
int dis[100][100], dp[1000000];
bool a[50000], dft[50000], used[50000];
std::queue < int > q;
inline void _min(int& x, const int y) {x = x > y ? y : x; }
int main()
{
scanf("%d%d%d", &n, &k, &m);
for (register int i = 1; i <= k; ++i) scanf("%d", &x), a[x] = 1;
for (register int i = 1; i <= n + 1; ++i)
{
dft[i] = a[i - 1] ^ a[i];
if (dft[i]) c[++cnt] = i;
}
for (register int i = 1; i <= m; ++i) scanf("%d", &b[i]);
for (register int i = 1; i <= cnt; ++i)
{
for (register int j = 1; j <= n + 1; ++j) s[j] = used[j] = 0;
used[c[i]] = 1;
q.push(c[i]);
while (!q.empty())
{
x = q.front();
q.pop();
for (register int j = 1; j <= m; ++j)
{
xx = x + b[j];
if (xx <= n + 1 && !used[xx])
{
s[xx] = s[x] + 1;
used[xx] = 1;
q.push(xx);
}
xx = x - b[j];
if (xx > 0 && !used[xx])
{
s[xx] = s[x] + 1;
used[xx] = 1;
q.push(xx);
}
}
}
for (register int j = 1; j <= cnt; ++j) dis[i][j] = s[c[j]];
}
C = 1 << cnt;
for (register int i = 1; i < C; ++i) dp[i] = INF;
for (register int i = 0; i < C; ++i)
{
x = 0; X = 1;
while (X & i) X *= 2, ++x;
for (register int j = x + 1, J = 1 << j; j <= cnt; ++j, J *= 2)
if (!(J & i) && dis[x + 1][j + 1])
_min(dp[i | X | J], dp[i] + dis[x + 1][j + 1]);
}
printf("%d
", dp[C - 1]);
}