雅礼集训 2017 Day4 编码(2-sat)
题意
题目链接:https://loj.ac/problem/6036
思路
首先,有前缀关系的串不能同时存在,不难看出这是一个 2-sat 问题。先假设所有串都带问号,那么每一个字符串,我们可以把它的两种情况当成一个布尔值的 \(0/1\) 。然后互为前缀的串不能同时存在,最多连 \(n^2\) 条边,直接跑 2-sat 就在 \({\cal O}(n^2)\) 的时间内解决了本题。
由于是个 \(01\) 字符串的前缀问题,我们不难想到字典树。要是能在字典树上体现前缀关系就好了。我们先退而求其次,先表达链式结构。
我个人习惯用一个被划成两半的点来表示一个布尔值,左部分表示假,右部分表示真。就像这样:

我们现在的问题是,对于 \(n\) 个布尔值,如何通过 \(\mathcal O(n)\) 建图表示这些布尔值最多只能存在一个真。化成 2-sat 的语言:它们两两的与运算都为 \(0\) 。
这就是 2-sat 的前缀优化。再开一排新布尔值(用方形点来表示),采用下图方式连边:

千言万语都不如一张图讲的清楚。你会发现,不管哪个原布尔值取了 \(1\) ,都可以通过方点告诉其他节点,“这条链里已经有人是真了,你们其他人都不能是真。”
要特别注意的一点是,逆否边都是要连的,上图只是为了方便没有画出逆否边,具体写代码的时候也可以在连边函数中加上连逆否边防止忘连。
有机化学都白学了吗?快告诉我上面的链中的链节是什么?

不管有多长的链,都可以通过这种基团相连,处在同一链上的布尔值最多只有一个真。
那么,树上的做法也就很显然了。

上图就是一种树上的结构(例子举的不大好,请读者自行脑补一些树枝),它在字典树上看是这样的:

上图的字典树中插入了 \(00,00,00,001\) 四个串,树上的一条向上的路径中不会同时有多个为真的布尔值。好写起见,我们索性在插入完一个串后的在字典树上可持久化出一个位置,插入我们的基团。这样写的话插入字符串要按长度升序来插。
代码
#include<bits/stdc++.h>
#define FOR(i, x, y) for(int i = (x), i##END = (y); i <= i##END; ++i)
#define DOR(i, x, y) for(int i = (x), i##END = (y); i >= i##END; --i)
template<typename T, typename _T> inline bool chk_min(T &x, const _T &y) {return y < x ? x = y, 1 : 0;}
template<typename T, typename _T> inline bool chk_max(T &x, const _T &y) {return x < y ? x = y, 1 : 0;}
typedef long long ll;
const int N = 500005 * 6;
const int M = 500005 * 16;
template<const int N, const int M, typename T> struct Linked_List
{
int head[N], nxt[M], tot; T to[M];
Linked_List() {clear();}
T &operator [](const int x) {return to[x];}
void clear() {memset(head, -1, sizeof(head)), tot = 0;}
void add(int u, T v) {to[tot] = v, nxt[tot] = head[u], head[u] = tot++;}
#define EOR(i, G, u) for(int i = G.head[u]; ~i; i = G.nxt[i])
};
Linked_List<N, M, int> G;
int rt, tot, ch[N][2];
int dfn[N], low[N], stk[N], bel[N], dfn_idx, scc, tp;
std::string str[N]; int ord[N];
int n, m;
bool cmp(int a, int b) {return str[a].length() < str[b].length();}
void tarjan(int u, int fa_e)
{
dfn[u] = low[u] = ++dfn_idx, stk[++tp] = u;
EOR(i, G, u)
{
if(i == (fa_e ^ 1)) continue;
int v = G[i];
if(!dfn[v])
{
tarjan(v, i);
chk_min(low[u], low[v]);
}
else if(!bel[v] && dfn[v] < dfn[u])
chk_min(low[u], dfn[v]);
}
if(dfn[u] == low[u])
{
scc++;
do bel[stk[tp]] = scc; while(stk[tp--] != u);
}
}
void link(int u, int v)
{
G.add(u, v), G.add(v ^ 1, u ^ 1);
}
void insert(std::string &str, int t)
{
if(!rt) rt = ++tot;
int k = rt, las;
FOR(i, 0, (int)str.length() - 1)
{
if(!ch[k][str[i] - '0'])
{
ch[k][str[i] - '0'] = ++tot;
link(k << 1 | 1, ch[k][str[i] - '0'] << 1 | 1);
}
las = k, k = ch[k][str[i] - '0'];
}
tot++;
link(k << 1 | 1, tot << 1 | 1);
link(t, tot << 1 | 1);
link(k << 1 | 1, t ^ 1);
ch[las][str[(int)str.length() - 1] - '0'] = tot;
}
int main()
{
scanf("%d", &n);
rt = 0, tot = n;
FOR(i, 1, n) std::cin >> str[i], ord[i] = i;
std::sort(ord + 1, ord + 1 + n, cmp);
FOR(i, 1, n)
{
bool flg = 0;
FOR(j, 0, (int)str[ord[i]].length() - 1) if(str[ord[i]][j] == '?')
{
str[ord[i]][j] = '0', insert(str[ord[i]], i << 1);
str[ord[i]][j] = '1', insert(str[ord[i]], i << 1 | 1);
flg = 1;
break;
}
if(!flg)
{
insert(str[ord[i]], i << 1);
link(i << 1 | 1, i << 1);
}
}
FOR(i, 2, tot << 1 | 1) if(!dfn[i]) tarjan(i, -1);
FOR(i, 1, tot) if(bel[i << 1] == bel[i << 1 | 1])
{
puts("NO");
return 0;
}
puts("YES");
return 0;
}
雅礼集训 2017 Day4 编码(2-sat)的更多相关文章
- Loj 6036 「雅礼集训 2017 Day4」编码 - 2-sat
题目传送门 唯一的传送门 题目大意 给定$n$个串,每个串只包含 ' .问是否可能任意两个不同的串不满足一个是另一个的前缀. 2-sat的是显然的. 枚举每个通配符填0还是1,然后插入Trie树. 对 ...
- 【LOJ6036】 「雅礼集训 2017 Day4」编码
传送门 LOJ Solution 因为?只有两种可能为0,1,所以就把这两个串搞出来. 那么现在?取0和?取1不能并存,前缀不能并存,所以就是一个\(2-SAT\),现在问题在于这个东西可能会有很多条 ...
- LOJ #6036.「雅礼集训 2017 Day4」编码 Trie树上2-sat
记得之前做过几道2-sat裸体,以及几道2-sat前缀优化建图,这道题使用了前缀树上前缀树优化建图.我们暴力建图肯定是n^2级别的,那么我们要是想让边数少点,就得使用一些骚操作.我们观察我们的限制条件 ...
- loj 6037 「雅礼集训 2017 Day4」猜数列 - 动态规划
题目传送门 传送门 题目大意 有一个位置数列,给定$n$条线索,每条线索从某一个位置开始,一直向左或者向右走,每遇到一个还没有在线索中出现的数就将它加入线索,问最小的可能的数列长度. 依次从左到右考虑 ...
- 2018.10.27 loj#6035. 「雅礼集训 2017 Day4」洗衣服(贪心+堆)
传送门 显然的贪心题啊...考试没调出来10pts滚了妙的一啊 直接分别用堆贪心出洗完第iii件衣服需要的最少时间和晾完第iii件衣服需要的最少时间. 我们设第一个算出来的数组是aaa,第二个是bbb ...
- LOJ#6035. 「雅礼集训 2017 Day4」洗衣服
传送门 先处理出每一件衣服最早什么时候洗完,堆+贪心即可 然后同样处理出每件衣服最早什么时候烘干 然后倒序相加取最大值 # include <bits/stdc++.h> using na ...
- LOJ #6037.「雅礼集训 2017 Day4」猜数列 状压dp
这个题的搜索可以打到48分…… #include <cstdio> #include <cstring> #include <algorithm> ; bool m ...
- LOJ #6035.「雅礼集训 2017 Day4」洗衣服 贪心
这道题的贪心好迷啊~我们对于两个过程进行单独贪心,然后再翻转一个,把这两个拼起来.先说一下单独贪心,单独贪心的话就是用一个堆,每次取出最小的,并且把这个最小的加上他单次的,再放进去.这样,我们得到的结 ...
- 「6月雅礼集训 2017 Day4」qyh(bzoj2687 交与并)
原题传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2687 [题目大意] 给出若干区间,求一个区间的大于等于2的子集,使得 |区间并| 和 | ...
随机推荐
- KVM virsh console
(1) 查看正在运行的虚拟机 root@kvm:~# virsh list Id Name State ------------------------------------------------ ...
- Abap内表
什么是内表:内表是内存中建立的一个临时表,你可以在程序运行时对表中的数据进行,插入,修改,删除等操作,程序跑完了,就会被释放. 定义类型:通过types开头定义 TYPES: BEGIN OF lin ...
- 利用正则来查找字符串中第n个匹配字符索引
1.string.IndexOf()方法可以获得第一个匹配项的索引 2.要获取第n个匹配项的索引: 方法1:利用IndexOf方法循环获取. 方法2:用正则来查找. System.Text.Regu ...
- 拒绝CPU挖矿矿工有责
长期以来CPU挖矿给挖矿行业带来持久的负面影响,因为CPU是电脑的核心设备,一挖矿就干不了别的了,大家是否可以达成共识拒绝CPU挖矿? 显卡挖矿刚好构建在不影响大众的日常工作生活对电脑的需求之上,家用 ...
- c# System.Net.Sockets =》TcpListener用法
private TcpListener _listener;#region 初始化 listener public override void Init() { try { DevInfo.Read ...
- RookeyFrame在线新增模块
今天给大家演示下在线新增模块的功能,在线新增模块跟在vs中写model实体类区别不大,线上新增少了手动初始化的过程,新增后模块同样具备新增.修改.删除.查看.导入.导出.复制.批量编辑.回收站.草稿箱 ...
- MySQL快速入门及常用命令
数据库 笔记内容 SQL语言共分为四大类:数据查询语言DQL,数据操纵语言DML,数据定义语言DDL,数据控制语言DCL. 1. 数据查询语言DQL 数据查询语言DQL基本结构是由SELECT子句,F ...
- C#中全局作用域的常量、字段、属性、方法的定义与使用
场景 在开发中,经常会有一些全局作用域的常量.字段.属性.方法等. 需要将这些设置为全局作用域保存且其实例唯一. 注: 博客主页: https://blog.csdn.net/badao_liuman ...
- vs2015 创建MVC项目
直接上图吧! 第一步:新建项目 第二步:选择模板 第三步:系统自动生成项目文件 第四步:创建控制器(C):找到Controllers文件夹->右键->添加->控制器 第五步:添加控制 ...
- tomcat的基本应用
1.JVM基本介绍 JAVA编译型 ---> 编译 C 编译型---> linux --->编译一次 windows --->编译一次 macos ubuntu 跨平台 移值型 ...