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) 串的最小步数。

分类讨论,进行下一步转化:

  1. 将一对 (0) 取反,显然会使答案更劣,不可能发生。
  2. 将一个 (0) 一个 (1) 取反,可看做原有的 (1) 移动至 (0) 的位置。
  3. 将一对 (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的主要内容,如果未能解决你的问题,请参考以下文章

CF79D Password(P3943 星空)

连接MySQL出现错误:ERROR 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: YES)(代码片段

CF126B Password

修改MySQL密码报错“ERROR 1819 (HY000): Your password does not satisfy the current policy requirements“(代码片段

CF418E Tricky Password(未完待续)

[CF418E] Tricky Password