Loading

luoguP6965 [NEERC2016]Binary Code 题解

题目链接

这个题目的流程还是非常顺利的,难度在代码能力。

看到每个字符串最多只有一个不确定的地方,也就是说明每个字符串最多两种可能。
一个非常显然的想法就是 \(\text {2-SAT}\) ,直接暴力枚举出所有不能同时存在的组合,这样是 \(O(n^2)\sim O(n^3)\) 的。

考虑怎么优化这个 \(\text{2-SAT}\) 的连边过程。
对于每一个字符串,我们把不确定的地方分别放入 \(1\)\(0\) 后塞到一个 \(\text{Trie}\) 树里面。
特别的,对于没有不确定位置的字符串我们直接插入到 \(\text{Trie}\) 具体实现可以看代码。

容易知道的是,对于一个标记点 \(p\) ,所有在它子树中的标记点都是不可行的状态,也就是说存在前缀的关系。
到这里可以想到直接去遍历它的子树,然后暴力建边,但是仍然不够优秀。
我们尝试着把 \(\text{Trie}\) 树拉出 \(\text{dfn}\) 序,那么对于一个节点 \(i\) ,能够对它造成影响的是 dfn[i]~dfn[i+siz[i]-1]

把所有的标记点按照 \(\text{dfn}\) 序排序,那么知道范围后就可以二分得出一段连边的区间 \([l,r]\)
然后可以直接线段树优化建图,直接跑 \(\text{2-SAT}\) 就可以了。

但是事实证明,已经做到上述的情况时,想把它卡掉还是比较困难的,直接写暴力连边可以过 70+ 个点 。
因为不想写线段树优化建图,所以来点玄学的优化:记录每一个字符串出现的次数。
当其中的一个字符串出现次数大于 \(2\) 的时候,直接判断无解。

实测证明,开了 \(O_2\) 以后跑点得很快。

点击查看代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cctype>
#include <string>
#include <map>

#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout)

using std::pair;
using std::string;
using std::make_pair;

#define pii pair<int, int>
#define mp make_pair

using ll = long long;
const int inf = 1e9;
const ll infll = 0ll + 1e18;

const int N = 5e5 + 5;

int n, tot, dfn[2 * N], dfnnum, col[2 * N], sta[N * 2], top, low[N * 2], colnum;
string s[N], ss;
std::vector <int> dis[N * 2];

struct Node {
  int id, tag, dfn;
  Node (int _id = 0, int _tag = 0, int _dfn = 0) {
    id = _id; tag = _tag; dfn = _dfn;
  }
  friend bool operator<(const Node &p, const Node &q) {
    return p.dfn < q.dfn;
  }
} node[N * 2];

namespace Trie {
int son[N][2], pnum = 1, siz[N], dfn[N], dfnnum;
std::vector <pii> mark[N];
std::map <pii, int> point, p;

inline void insert(int, string, int);
inline void dfs(int, int);
inline void changePtoDfn();
}

inline int get(int n, int tag) {return n * 2 - tag;}
inline void solve_addline(int, int);

inline void Tarjan(int now) {
  dfn[now] = low[now] = ++dfnnum;
  sta[++top] = now;
  for (int t : dis[now]) {
    if (dfn[t] == 0) {
      Tarjan(t);
      low[now] = std::min(low[now], low[t]);
    } else if (col[t] == 0) {
      low[now] = std::min(low[now], dfn[t]);
    }
  }
  if (low[now] == dfn[now]) {
    col[now] = ++colnum;
    while (sta[top] != now) {
      col[sta[top]] = colnum;
      top --;
    }
    top --;
  }
}

signed main(void) {
  // File("1");
  std::ios::sync_with_stdio(false);
  std::cin.tie(0), std::cout.tie(0);
  std::cin >> n;
  for (int i = 1, pos, len; i <= n; i++) {
    std::cin >> s[i];
    ss = s[i]; pos = -1; len = ss.size();
    for (int j = 0; j < len; j ++)
      if (ss[j] == '?') {pos = j; break;}
    if (pos == -1) {
      Trie::insert(i, ss, 0);
      Trie::insert(i, ss, 1);
    } else {
      ss[pos] = '0'; 
      Trie::insert(i, ss, 0);
      ss[pos] = '1'; 
      Trie::insert(i, ss, 1);
    }
  }
  Trie::dfs(1, 0);
  Trie::changePtoDfn();
  for (int i = 1; i <= n; i++) {
    node[++tot] = Node(i, 0, Trie::point[mp(i, 0)]);
    node[++tot] = Node(i, 1, Trie::point[mp(i, 1)]);
  }
  std::sort(node + 1, node + 1 + tot);
  for (int i = 1; i <= n; i ++) {
    solve_addline(i, 0);
    solve_addline(i, 1);
  }
  for (int i = 1; i <= n * 2; i ++)
    if (dfn[i] == 0) Tarjan(i);
  // for (int i = 1; i <= n; i++)
  //   std::cout << col[get(i, 0)] << " " << col[get(i, 1)] << std::endl;
  for (int i = 1; i <= n; i++) 
    if (col[get(i, 0)] == col[get(i, 1)]) {
      std::cout << "NO" << std::endl;
      return 0;
    }
  std::cout << "YES" << std::endl;
  for (int i = 1, len; i <= n; i++) {
    ss = s[i]; len = ss.size();
    int num = col[get(i, 0)] < col[get(i, 1)] ? 0 : 1;
    for (int j = 0; j < len; j ++)
      if (ss[j] == '?') ss[j] = num + '0';
    std::cout << ss << std::endl;
  }
  return 0;
}

inline void solve_addline(int id, int tag) {
  int dfn, size, rightnum, leftnum, l, r;
  int L = inf, R = -inf;
  size = Trie::siz[Trie::p[mp(id, tag)]];
  dfn = Trie::point[mp(id, tag)];
  rightnum = dfn + size - 1;
  leftnum = dfn;
  l = 1, r = tot;
  while (l <= r) {
    int mid = (l + r) / 2;
    if (node[mid].dfn >= leftnum) {
      L = std::min(L, mid);
      r = mid - 1;
    } else l = mid + 1;
  }
  l = 1, r = tot;
  while (l <= r) {
    int mid = (l + r) / 2;
    if (node[mid].dfn <= rightnum) {
      R = std::max(R, mid);
      l = mid + 1;
    } else r = mid - 1;
  }
  // if (id % 10000 == 0) std::cerr << id << std::endl;
  // std::cerr << L << " " << R << std::endl;
  for (int i = L, Id, Tag; i <= R; i ++) {
    Id = node[i].id; 
    Tag = node[i].tag;
    if (id == Id && Tag == tag) continue;
    // std::cout << id << " " << tag << " " << Id << " " << Tag << std::endl;
    dis[get(id, tag)].emplace_back(get(Id, 1 - Tag));
  }
  return ;
}

namespace Trie {
inline void insert(int id, string s, int tag) {
  int len = s.size(), now = 1;
  for (int i = 0; i < len; i++) {
    int bit = s[i] - '0';
    if (son[now][bit] == 0) 
      son[now][bit] = ++pnum;
    now = son[now][bit];
  }
  mark[now].emplace_back(mp(id, tag));
  point[mp(id, tag)] = now;
  p[mp(id, tag)] = now;
  return ;
}
inline void dfs(int now, int father) {
  dfn[now] = ++ dfnnum;
  siz[now] = 1;
  for (int i = 0; i <= 1; i ++) {
    if (son[now][i] == 0) continue;
    dfs(son[now][i], now);
    siz[now] += siz[son[now][i]];
  }
}
inline void changePtoDfn() {
  for (int i = 1; i <= n; i ++) {
    point[mp(i, 0)] = dfn[point[mp(i, 0)]];
    point[mp(i, 1)] = dfn[point[mp(i, 1)]];
  }
  return ;
}
}

posted @ 2022-10-02 17:51  Aonynation  阅读(52)  评论(0编辑  收藏  举报