题目链接

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. ForwardPriceSimulationService

    using System;using System.Collections.Generic;using System.Linq;using System.Text; using System.Coll ...

  2. PHP 微信公众号开发 - 消息推送

    项目微信公众号开发,需要做用户消息推送,记录下来以便日后使用 1,接上一篇文章,可以查看如何获取用户openid PHP 微信公众号开发 - 获取用户信息 2,添加模板消息 3,查看模板详情 根据模板 ...

  3. Golang使用pkg-config自动获取头文件和链接库的方法

    为了能够重用已有的C语言库,我们在使用Golang开发项目或系统的时候难免会遇到Go和C语言混合编程,这时很多人都会选择使用cgo. 话说cgo这个东西可算得上是让人又爱又恨,好处在于它可以让你快速重 ...

  4. 通过 cygwin64 自己编译对应的 Tera Term cyglaunch.exe

    步骤如下: 将 cygterm+.tar.gz解压到任意目录,当然要cygwin容易操作.(本例直接放到$HOME目录下,启动cygwin后的默认目录,如果之前没有更改的话) 将 Makefile 中 ...

  5. 我的Jquery参考词典

    由于工作主要用到Asp.net Mvc+Jquery,最近也看了一些Jquery的书籍,在此总结以备回顾. 已读书籍:<Jquery In Action> 主要讲了些Jquery语法以及A ...

  6. 教你如何学python

    首先,你要有自信心,要明确学习目的.学Python,可以解决在软件使用中所遇到的问题,可以为找到理想工作添加重要砝码.还能锻炼思维,使我们的逻辑思维更加严密:能不断享受到创新的乐趣,将走在高科技的前沿 ...

  7. kv数据库对比总结

    集群型: hbase Cassandra scylladb redis类: redis + twemproxy codis 持久型: pika ssdb

  8. 【转】Leader-Follower线程模型

    上图就是L/F多线程模型的状态变迁图,共6个关键点: (1)线程有3种状态:领导leading,处理processing,追随following (2)假设共N个线程,其中只有1个leading线程( ...

  9. 域名通过infopath访问webservice出现401错误

    解决办法: 跟服务器有关,需要再每台服务器进行以下配置 New-ItemProperty HKLM:\System\CurrentControlSet\Control\Lsa -Name " ...

  10. SpringBoot :docker

    Docker 是一个开源的应用容器引擎 docker官方网站:https://hub.docker.com/ $部署docker到Linux系统 1.准备一个Linux系统的虚拟机或者物理机 本例所使 ...