CF1007D. Ants(樹鏈剖分+線段樹+2-SAT及字首優化建圖)
阿新 • • 發佈:2018-12-21
題目連結
https://codeforces.com/problemset/problem/1007/D
題解
由於問題的本質是給定許多二元集合,判斷是否能從每一個二元集合中選出一個元素,使得所有選出的元素合法,因此考慮使用 2-SAT 解決該問題。
不難發現,使用 2-SAT 解決該問題的複雜度瓶頸在於建圖。
我們為每一種顏色 \(i\) 對應的兩條路徑賦上編號。首先,我們需要為每一條樹邊記錄包含該條邊的所有路徑的編號。可以將原樹樹鏈剖分之後,按結點的 dfs 序建出線段樹,將路徑的編號新增到線段樹對應的結點上。這樣,包含一條樹邊的所有路徑編號儲存在該邊線上段樹上對應的葉子結點以及該葉子結點的各級祖先上。因此,我們只需要通過建圖來保證線段樹的每一個葉子結點及其各級祖先包含的所有編號中,最多隻能選擇一個編號即可。
考慮使用字首優化建圖。
簡單說來,字首優化建圖常用來處理某個命題集合中最多有一個命題成立或不成立的情況(不失一般性地,接下來只分析最多有一個命題成立的情況,在本題中,命題成立即為選擇對應編號)。假設這些命題的編號為 \(1 \sim x\),那麼我們新建 \(x\) 個結點,用這些結點來表示集合的某個字首的所有命題是否均不成立,即:這些結點中,第 \(i\) 個結點若為真,則命題 \(1 \sim i\) 均不成立,否則命題 \(1 \sim i\) 中存在一個命題成立。
定義一次建邊 \((u \rightarrow v)\) 為:建 \((u\) 為真 \(\rightarrow\)
- \(1 \sim i\) 的所有命題均不成立 \(\rightarrow\) \(1 \sim i - 1\) 的所有命題均不成立
- 命題 \(i\) 成立 \(\rightarrow\) \(1 \sim i - 1\) 的所有命題均不成立
- \(1 \sim i\) 的所有命題存在成立 \(\rightarrow\) 命題 \(i + 1\) 不成立
本題的建圖略有不同,由於線上段樹上,所有的字首本身就構成了一個樹形結構,因此相同的字首可以共用結點。不難發現,最後建出的總結點數(及邊數)和線段樹上儲存的編號總數同階,為 \(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;
}