[Codeforces 79D]Password(BFS + 状压 DP)

Address

Meaning

  • n n 枚硬币,一开始全部正面朝上
  • 给定一个大小为 l l 的正整数集合 a a
  • 每次可以选定一个长度 w a w\in a ,将一段连续的 w w 枚硬币全部翻转
  • 求使得最后仅有给定的 k k 枚硬币 a 1 , a 2 , . . . , a k a_1,a_2,...,a_k 反面朝上的最小翻转次数
  • 1 n 10000 1\le n\le10000 1 k 10 1\le k\le10 1 l 100 1\le l\le100

Solution

  • 一道思路非常棒的好题
  • 以下为了方便讨论,将反面朝上记作 1 1 ,正面朝上记作 0 0
  • 将最终硬币状态的 01 01 序列(反面朝上处为 1 1 ,正面朝上处为 0 0 )作一个异或意义下的差分
  • 那么原序列的一个长度为 w w 的区间异或 1 1
  • 就等价于差分序列中两个距离为 w w 的位置异或 1 1 (这个位置可以为 n + 1 n+1
  • k k 只有 10 10 ,差分后 1 1 的个数最多也就 2 k 2k
  • 下面转化问题:一个含有不超过 2 k 2k 1 1 的差分序列,每次可以选一个 w a w\in a 选两个距离为 w w 的位置异或 1 1 ,求把所有数变成 0 0 的最少次数
  • 而在任何时候,差分序列中两个当前为 0 0 的位置异或 1 1 显然不优
  • 差分序列中两个当前为 1 1 的位置异或 1 1 ,就能将这两个 1 1 同时变成 0 0
  • 而如果这两个位置当前一个为 1 1 一个为 0 0 ,那么这两个位置异或 1 1 ,等价于 1 1 0 0 的方向平移了 w w 个单位( w w 为两个位置间的距离)
  • 注意上面的位置中也包括 n + 1 n+1
  • 预处理出 g [ i ] [ j ] g[i][j] h [ i ] h[i]
  • g [ i ] [ j ] g[i][j] 表示一个 1 1 i i ,另一个 1 1 j j ,在不影响其他位置的情况下把这两个 1 1 变成 0 0 的最少次数
  • h [ i ] h[i] 表示一个 1 1 i i ,在不影响其他位置的情况下把这个 i i 变成 0 0 (位置 n + 1 n+1 会被取反)
  • 注意我们这里会用到的 i , j i,j 仅仅是差分序列上最多 2 k 2k 个为 1 1 的位置
  • 考虑建图: 1 1 n + 1 n+1 n + 1 n+1 个点,对于任意 w a w\in a ,如果 i j = w |i-j|=w 则连边 ( i , j ) (i,j) ,边权为 1 1
  • g [ i ] [ j ] g[i][j] 可以固定一个点 i i ,以 i i 为源点求单源最短路,就能求出所有的 g [ i ] [ ] g[i][]
  • 注意边权为 1 1 ,可以 BFS 代替最短路
  • h [ i ] h[i] 则是以 n + 1 n+1 为源点 BFS
  • 有了最短路之后,我们可以开始 DP 辣
  • f [ S ] f[S] 表示将差分序列的下标集合 S S 进行取反,其他位置不受影响的最小步数( S S 中仅包含不超过 2 k 2k 个位置)
  • 显然 f [ ] = 0 f[\emptyset]=0
  • (1)枚举 i , j S , i j i,j\in S,i\ne j ,表示 i i j j 位置一起被取反,其他位置不受影响
  • f [ S ] = min ( f [ S ] , f [ S i j ] + g [ i ] [ j ] ) f[S]=\min(f[S],f[S-i-j]+g[i][j])
  • (2)枚举 i S i\in S ,表示 i i 借助位置 n + 1 n+1 实现取反,其他位置不受影响
  • f [ S ] = min ( f [ S ] , f [ S i ] + h [ i ] ) f[S]=\min(f[S],f[S-i]+h[i])
  • 转移时可以假定 i i S S 内的第一个元素(因为 S S 内所有的位置都必须被取反)
  • 最后答案显然为 f [ a l l ] f[all] a l l all 为最终状态差分 01 01 序列上 1 1 的位置集合
  • 复杂度 O ( k n l + 2 2 k × k ) O(knl+2^{2k}\times k)

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

const int N = 1e4 + 5, E = 25, M = 205, C = (1 << 20) + 5, INF = 0x3f3f3f3f;

int n, k, m, arr[N], a[M], tot, w[E], g[E][E], dis[N], len, que[N],
f[C], Cm, dn[C];

void bfs(int S)
{
	memset(dis, INF, sizeof(dis));
	dis[que[len = 1] = S] = 0;
	for (int i = 1; i <= len; i++)
	{
		int u = que[i];
		for (int j = 1; j <= m; j++)
		{
			int v = u + a[j];
			if (v < 1 || v > n + 1 || dis[v] < INF) continue;
			dis[que[++len] = v] = dis[u] + 1;
		}
	}
}

int main()
{
	n = read(); k = read(); m = read();
	for (int i = 1; i <= k; i++) arr[read()] = 1;
	for (int i = n; i; i--) arr[i] ^= arr[i - 1];
	for (int i = 1; i <= m; i++)
		a[i] = read(), a[i + m] = -a[i];
	m <<= 1;
	for (int i = 1; i <= n; i++)
		if (arr[i]) w[++tot] = i;
	for (int i = 1; i <= tot; i++)
	{
		bfs(w[i]);
		for (int j = 1; j <= tot; j++)
			g[i][j] = dis[w[j]];
	}
	bfs(n + 1);
	memset(f, INF, sizeof(f));
	f[0] = 0;
	Cm = 1 << tot;
	for (int i = 1; i <= tot; i++) dn[1 << i - 1] = i;
	for (int S = 1; S < Cm; S++)
	{
		int id = dn[S & -S], T = S ^ (S & -S);
		f[S] = Min(f[S], f[T] + dis[w[id]]);
		for (int i = 1; i <= tot; i++)
			if ((T >> i - 1) & 1)
				f[S] = Min(f[S], f[T ^ (1 << i - 1)] + g[id][i]);
	}
printf("%d\n", f[Cm - 1] == INF ? -1 : f[Cm - 1]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/85707993
今日推荐