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 ;
}
}