洛谷 P3943 星空

题意简述

有一个长度为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\n", dp[C - 1]);
}
posted @ 2018-10-23 20:36  xuyixuan  阅读(187)  评论(0编辑  收藏  举报