题目链接

https://codeforces.com/problemset/problem/1007/D

题解

这道题本身并不难,这里只是记录一下 2-SAT 的前缀优化建图的相关内容。

由于问题的本质是给定许多二元集合,判断是否能从每一个二元集合中选出一个元素,使得所有选出的元素合法,因此考虑使用 2-SAT 解决该问题。

不难发现,使用 2-SAT 解决该问题的复杂度瓶颈在于建图。

我们为每一种颜色 \(i\) 对应的两条路径赋上编号。首先,我们需要为每一条树边记录包含该条边的所有路径的编号。可以将原树树链剖分之后,按结点的 dfs 序建出线段树,将路径的编号添加到线段树对应的结点上。这样,包含一条树边的所有路径编号储存在该边在线段树上对应的叶子结点以及该叶子结点的各级祖先上。因此,我们只需要通过建图来保证线段树的每一个叶子结点及其各级祖先包含的所有编号中,最多只能选择一个编号即可。

考虑使用前缀优化建图。

简单来说,前缀优化建图常用来处理某个命题集合中最多有一个命题成立或不成立的情况(不失一般性地,接下来只分析最多有一个命题成立的情况,在本题中,命题成立即为选择对应编号)。假设这些命题的编号为 \(1 \sim x\),那么我们新建 \(x\) 个结点,用这些结点来表示集合的某个前缀的所有命题是否均不成立,即:这些结点中,第 \(i\) 个结点若为真,则命题 \(1 \sim i\) 均不成立,否则命题 \(1 \sim i\) 中存在一个命题成立。

定义一次建边 \((u \rightarrow v)\) 为:建 \((u\) 为真 \(\rightarrow\) \(v\) 为真\()\) 与 \((v\) 为假 \(\rightarrow u\) 为假\()\) 两条对称边。那么我们只需要建如下三类边即可:

  • \(1 \sim i\) 的所有命题均不成立 \(\rightarrow\) \(1 \sim i - 1\) 的所有命题均不成立
  • 命题 \(i\) 成立 \(\rightarrow\) \(1 \sim i\) 的所有命题存在成立
  • 命题 \(i\) 成立 \(\rightarrow\) \(1 \sim i - 1\) 的所有命题均不成立

本题的建图略有不同,由于在线段树上,所有的前缀本身就构成了一个树形结构,因此相同的前缀可以共用结点。不难发现,最后建出的总结点数(及边数)和线段树上存储的编号总数同阶,为 \(O(m \log^2 n)\)。由于初始在线段树上添加标记的时间复杂度也为 \(O(m \log^2 n)\),因此总时间复杂度为 \(O(m \log^2 n)\)。

代码

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10, maxnode = N * 60;

void cmin(int& x, int y) {
if (x > y) {
x = y;
}
} int n, m, pa[N], dep[N], size[N], heavy[N], dfn[maxnode], dfn_cnt, top[N], node_cnt, low[maxnode], sccno[maxnode], scc;
vector<int> graph[N], graph_t[maxnode], nodes[N << 2], stack_t; void dfs1(int u, int pa) {
size[u] = 1;
for (auto v : graph[u]) {
if (v != pa) {
::pa[v] = u;
dep[v] = dep[u] + 1;
dfs1(v, u);
size[u] += size[v];
if (size[v] > size[heavy[u]]) {
heavy[u] = v;
}
}
}
} void dfs2(int u, int t) {
top[u] = t;
dfn[u] = ++dfn_cnt;
if (heavy[u]) {
dfs2(heavy[u], t);
for (auto v : graph[u]) {
if (v != pa[u] && v != heavy[u]) {
dfs2(v, v);
}
}
}
} void add_edge(int u, int v) {
graph_t[u].push_back(v);
graph_t[v ^ 1].push_back(u ^ 1);
} #define lo (o<<1)
#define ro (o<<1|1) void modify(int l, int r, int o, int ql, int qr, int id) {
if (ql <= l && r <= qr) {
nodes[o].push_back(id);
} else {
int mid = l + r >> 1;
if (ql <= mid) {
modify(l, mid, lo, ql, qr, id);
} if (qr > mid) {
modify(mid + 1, r, ro, ql, qr, id);
}
}
} void build(int l, int r, int o, int lastid) {
int ql = ++node_cnt;
int qr = (node_cnt += nodes[o].size());
if (qr > ql) {
add_edge(qr << 1 | 1, qr - 1 << 1 | 1);
} else if (lastid) {
add_edge(ql << 1 | 1, lastid << 1 | 1);
}
for (int i = 0; i < nodes[o].size(); ++i) {
int id = nodes[o][i];
if (i > 0) {
add_edge(ql + i << 1 | 1, ql + i - 1 << 1 | 1);
} else if (lastid) {
add_edge(ql << 1 | 1, lastid << 1 | 1);
}
add_edge(id, ql + i << 1);
if (i > 0) {
add_edge(ql + i - 1 << 1, id ^ 1);
} else if (lastid) {
add_edge(lastid << 1, id ^ 1);
}
}
if (l < r) {
int mid = l + r >> 1;
build(l, mid, lo, qr);
build(mid + 1, r, ro, qr);
}
} void add_tag(int u, int v, int id) {
for (; top[u] != top[v]; u = pa[top[u]]) {
if (dep[top[u]] < dep[top[v]]) {
swap(u, v);
}
modify(2, n, 1, dfn[top[u]], dfn[u], id);
}
if (dep[u] > dep[v]) {
swap(u, v);
}
if (dfn[u] < dfn[v]) {
modify(2, n, 1, dfn[u] + 1, dfn[v], id);
}
} void tarjan(int u) {
stack_t.push_back(u);
dfn[u] = low[u] = ++dfn_cnt;
for (auto v : graph_t[u]) {
if (!dfn[v]) {
tarjan(v);
cmin(low[u], low[v]);
} else if (!sccno[v]) {
cmin(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
++scc;
while (1) {
int x = stack_t.back();
stack_t.pop_back();
sccno[x] = scc;
if (x == u) {
break;
}
}
}
} int main() {
scanf("%d", &n);
for (int i = 1; i < n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
graph[u].push_back(v);
graph[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 1);
scanf("%d", &m);
node_cnt = m;
for (int i = 1; i <= m; ++i) {
int a, b, c, d;
scanf("%d%d%d%d", &a, &b, &c, &d);
add_tag(a, b, i << 1);
add_tag(c, d, i << 1 | 1);
}
build(2, n, 1, 0);
memset(dfn, 0, sizeof dfn);
dfn_cnt = 0;
for (int i = 2; i <= (node_cnt << 1 | 1); ++i) {
if (!dfn[i]) {
tarjan(i);
}
}
for (int i = 1; i <= m; ++i) {
if (sccno[i << 1] == sccno[i << 1 | 1]) {
return puts("NO"), 0;
}
}
puts("YES");
for (int i = 1; i <= m; ++i) {
printf("%d\n", sccno[i << 1] < sccno[i << 1 | 1] ? 1 : 2);
}
return 0;
}

CF1007D. Ants(树链剖分+线段树+2-SAT及前缀优化建图)的更多相关文章

  1. 【BZOJ-2325】道馆之战 树链剖分 + 线段树

    2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 1153  Solved: 421[Submit][Statu ...

  2. 【BZOJ2243】[SDOI2011]染色 树链剖分+线段树

    [BZOJ2243][SDOI2011]染色 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的 ...

  3. BZOJ2243 (树链剖分+线段树)

    Problem 染色(BZOJ2243) 题目大意 给定一颗树,每个节点上有一种颜色. 要求支持两种操作: 操作1:将a->b上所有点染成一种颜色. 操作2:询问a->b上的颜色段数量. ...

  4. POJ3237 (树链剖分+线段树)

    Problem Tree (POJ3237) 题目大意 给定一颗树,有边权. 要求支持三种操作: 操作一:更改某条边的权值. 操作二:将某条路径上的边权取反. 操作三:询问某条路径上的最大权值. 解题 ...

  5. bzoj4034 (树链剖分+线段树)

    Problem T2 (bzoj4034 HAOI2015) 题目大意 给定一颗树,1为根节点,要求支持三种操作. 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子 ...

  6. HDU4897 (树链剖分+线段树)

    Problem Little Devil I (HDU4897) 题目大意 给定一棵树,每条边的颜色为黑或白,起始时均为白. 支持3种操作: 操作1:将a->b的路径中的所有边的颜色翻转. 操作 ...

  7. Aizu 2450 Do use segment tree 树链剖分+线段树

    Do use segment tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.bnuoj.com/v3/problem_show ...

  8. 【POJ3237】Tree(树链剖分+线段树)

    Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edg ...

  9. HDU 2460 Network(双连通+树链剖分+线段树)

    HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链 ...

  10. bzoj2243[SDOI2011]染色 树链剖分+线段树

    2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 9012  Solved: 3375[Submit][Status ...

随机推荐

  1. [Selenium]计算坐标进行拖拽,重写dragAndDropOffset

    //@author jzhang6 public void dragAndDropOffset(WebDriver driver,WebElement dragableEl, WebElement d ...

  2. 给tabhost加上点击监听,不是onTabChanged(String)监听

    给tabhost加上点击监听,不是onTabChanged(String)监听 2012-08-11 01:43 5209人阅读 评论(0) 收藏 举报 stringandroidlayoutnull ...

  3. laydate的使用

    理论可以看看:http://blog.csdn.net/u013372487/article/details/50534034 下载包:链接:http://pan.baidu.com/s/1kUNQ6 ...

  4. Oracle学习笔记(四)

    六.约束 1.约束的作用 定义规则:什么数据可以输入,什么不可以 确保完整性:数据的精确性和可靠性 2.Oracle五个重要的约束: 非空约束.主键约束.外键约束.检查约束.唯一约束. (1)非空约束 ...

  5. UVa 11925 Generating Permutations (构造法)

    题意:给定一个序列,让你从一个升序列变成该序列,并且只有两种操作,操作1:交换前两个元素,操作2:把第一个元素移动到最后. 析:一开始的时候吧,不会,还是看的题解,首先是要逆序来做,这样可能好做一点, ...

  6. HTTP请求头和响应头总结

    1)请求(客户端->服务端[request])     GET(请求的方式) /books/java.html(请求的目标资源) HTTP/1.1(请求采用的协议和版本号)     Accept ...

  7. Java反射API研究(1)——注解Annotation

    注解在表面上的意思,只是标记一下这一部分,最好的注解就是代码自身.而在java上,由于注解的特殊性,可以通过反射API获取,这种特性使得注解被广泛应用于各大框架,用于配置内容,代替xml文件配置. 要 ...

  8. Android-UIUtils-工具类

    UIUtils工具类,主要是处理和Activity有关,和界面显示层有关的公共方法: package common.library.utils; import android.app.Activity ...

  9. [转]RTH试用手记之“偶发信号观测”

    年初,罗德与施瓦茨公司(Rohde & Schwarz)推出了第一款的手持示波器,从指标上看,该示波器打破了传统手持器功能简单.指标水平低.结构粗糙的印象,取而代之达到了主流台式数字示波器的性 ...

  10. Creative Cloud 应用程序 | 直接下载

    https://helpx.adobe.com/cn/download-install/kb/creative-cloud-apps-download.html