CF79D Password
Posted luckyblock
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF79D Password相关的知识,希望对你有一定的参考价值。
知识点: DP,差分,Bfs
原题面
双倍经验 P3943 星空。
将此题代码交过去可直接 AC,但 P3943 数据较弱,没有卡掉错误的背包解法。
完全背包解法错误原因 详见 题解 P3943 【星空】 - Epworth 的博客。
题意简述
给定一长度为 (n) 的 (0) 串,给定 (k) 个 位置。
给出 (m) 个长度 (b_i),每次可选择一段长度为 (b_i) 的子序列取反。
求使 (k) 个位置变为 (1) ,其他位置仍为 (0) 的最小步数。
(1le nle 10^4, 1le mle 100, 1le b_ile n, kle 10)。
分析题意
发现题目等价于 一开始只有 (k) 个位置开着灯,使所有灯都关上的最小步数。
设关灯为 (0),开灯为 (1),以下均按照上述等价情况展开。
看到区间修改,考虑差分。
但此题为区间取反,一般的作差差分无法使用,考虑异或差分。
令 (b_i = a_i ext{xor} a_{i+1}),对于样例 1,有:
差分前 | ( 1 1 1 0 1 1 1 1 1 0) |
---|---|
差分后 | (10011000010) |
在差分后数组前添加一个 (0) 位置。
手玩后发现,差分后必然有偶数个 (1) 出现。
此时若进行区间取反,只会使区间两端位置改变,中间不变。
若将原序列中 (1sim 3) 取反,则有:
差分前 | ( 0 0 0 0 1 1 1 1 1 0) |
---|---|
差分后 | (00001000010) |
发现只有 (0,3) 两个位置的 (1) 被改变。
则问题转化为:
给定一有 (2k) 个位置为 (1) 的 01 串。
每次可选择一对距离为 (b_i) 的位置,将其取反。
求将其变为 (0) 串的最小步数。
分类讨论,进行下一步转化:
- 将一对 (0) 取反,显然会使答案更劣,不可能发生。
- 将一个 (0) 一个 (1) 取反,可看做原有的 (1) 移动至 (0) 的位置。
- 将一对 (1) 取反,可看做一个 (1) 移动到另一个的位置,两个 (1) 碰撞变成 (0)。
一定会发生偶数次 两个 (1) 碰撞的情况。
考虑每次使两个 (1) 碰撞的最小代价。
若已知两个 (1) 的位置为 (u,v),使其碰撞的代价即 使用 (b_i) 和 (-b_i) 凑出 (v - u) 的步数。
显然可以 bfs 预处理出碰撞每对 (1) 的花费。
问题转化为:
给定 (2k) 个物品,每次可取出一对物品。
取出每对物品的花费已知,求全取出的最小花费。
由于 (kle 10),显然可以压缩 (k) 个物品选/不选的状态。
接下来就是个很简单的状压了。
每次枚举两个不在集合中的物品 加入集合转移即可。
代码实现
//知识点:DP,差分,Bfs
/*
By:Luckyblock
异或差分 + Bfs + 状压DP
*/
#include <queue>
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxm = 110;
const int kMaxn = 1e4 + 10;
const int kInf = 1e9 + 2077;
//=============================================================
int n, k, m, cnt;
int pos[kMaxm], a[kMaxn], b[kMaxm];
int dis[kMaxn], map[kMaxn], cost[30][30];
int f[(1 << 21) + 10];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == ‘-‘) f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ ‘0‘);
return f * w;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
void Bfs(int s) { //预处理出碰撞每对 1 的花费。
std :: queue <int> q;
memset(dis, 63, sizeof (dis));
dis[s] = 0;
q.push(s);
while (! q.empty()) {
int u = q.front(); q.pop();
for (int i = 1; i <= m; ++ i) {
int v1 = u + b[i], v2 = u - b[i]; //v1 向右扩张,v2 向左。
if (v1 <= n && dis[v1] > kInf) dis[v1] = dis[u] + 1, q.push(v1);
if (v2 >= 1 && dis[v2] > kInf) dis[v2] = dis[u] + 1, q.push(v2);
}
}
for (int i = 1; i <= n; ++ i) {
if (map[i]) cost[map[s]][map[i]] = dis[i];
}
}
void Prepare() {
n = read(), k = read(), m = read();
for (int i = 1; i <= k; ++ i) a[read()] = 1;
for (int i = 0; i <= n; ++ i) a[i] ^= a[i + 1];
for (int i = 1; i <= m; ++ i) b[i] = read();
for (int i = 0; i <= n; ++ i) {
if (a[i]) map[i] = ++ cnt; //建立映射关系
}
for (int i = 0; i <= n; ++ i ) if (a[i]) Bfs(i);
}
//=============================================================
int main() {
Prepare();
memset(f, 63, sizeof (f)); f[0] = 0;
int all = (1 << cnt) - 1;
for (int i = 0; i < all; ++ i) {
if (f[i] > kInf) continue ;
for (int x = 1; x <= cnt; ++ x) {
if ((1 << (x - 1)) & i) continue ;
for (int y = x + 1; y <= cnt; ++ y) { //每次枚举两个 1 转移
if ((1 << (y - 1)) & i) continue ;
GetMin(f[i | (1 << (x - 1)) | (1 << (y - 1))],
f[i] + cost[x][y]);
}
}
}
printf("%d", f[all] < kInf ? f[all] : - 1);
return 0;
}
以上是关于CF79D Password的主要内容,如果未能解决你的问题,请参考以下文章
连接MySQL出现错误:ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: YES)(代码片段
修改MySQL密码报错“ERROR 1819 (HY000): Your password does not satisfy the current policy requirements“(代码片段