[CF1007D]Ants[2-SAT+树剖+线段树优化建图]

题意

我们用路径 \((u, v)\) 表示一棵树上从结点 \(u\) 到结点 \(v\) 的最短路径。

给定一棵由 \(n\) 个结点构成的树。你需要用 \(m\) 种不同的颜色为这棵树的树边染色,在这 \(m\) 种颜色中,第 \(i\) 种颜色有两条备选路径

\((a_i, b_i)\)\((c_i, d_i)\),你的任务是判断是否存在一种合法的染色方案,使得每种颜色 \(i\) 所对应的两条备选路径中都有至少一条满足:

该路径上的所有树边的颜色均为颜色 \(i\)。若存在,输出 YES,并依次输出每种颜色所对应的两条备选路径中,哪一条是满足要求的。

若输出 1,则表示路径 \((a_i, b_i)\) 是合法的,若输出 2,则表示路径 \((c_i, d_i)\) 是合法的);若不存在,输出 NO

\(2 \leq n \leq 10^5, 1 \leq m \leq 10^4, 1 \leq a_i, b_i, c_i, d_i \leq n, a_i \neq b_i, c_i \neq b_i\)

分析

  • 令一种颜色的两条路径互为逆命题。(如果两条边可以同时出现只出现一条一定可以满足)

  • 考虑一种暴力的做法:枚举经过一条边的所有路径,命题两两连边,这个过程可以前缀优化建图,但似乎还不够优秀。

  • 能不能更高效地将一条路径的影响记录到树上呢?容易联想到树剖,我们用树剖+线段树的方式将路径的影响加入线段树中 \({log}^2\) 个节点中并标记永久化,对线段树上每个节点的所有路径前缀优化建图。

  • 容易发现每个点(线段树上)的限制不仅来自当前节点,他的所有祖先和子树内的路径与他之间都只能选一个,所以节点的最后一个命题向两个儿子节点的第一个命题连边构成树形结构,就满足了每个点的限制。

  • 空间复杂度 \(O(m{log}^2n)\) ,时间复杂度 \(O(m{log}^2n)\)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define go(i, u, v) for(int (i) = head[(u)], (v) = e[(i)].to; (i); (i)=e[(i)].lst, (v)=e[(i)].to)
#define rep(i, a, b) for(int (i) = (a); (i) <= (b); ++(i))
#define pb push_back
inline int gi() {
    int x = 0,f = 1;
    char ch = getchar();
    while(!isdigit(ch)) {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) {
        x = (x << 3) + (x << 1) + ch - 48;
        ch = getchar();
    }
    return x * f;
}
template <typename T> inline void Max(T &a, T b){if(a < b) a = b;}
template <typename T> inline void Min(T &a, T b){if(a > b) a = b;}
const int N = 1e5 + 7, Nd = 6e6 + 7;
int n, edc;
int head[N], ndc;
struct edge {
    int lst, to;
    edge(){}edge(int lst, int to):lst(lst), to(to){}
}e[N << 1];
void Add(int a, int b) {
    e[++edc] = edge(head[a], b), head[a] = edc;
    e[++edc] = edge(head[b], a), head[b] = edc;
}
vector<int> G[Nd];
void lim(int a, int b) {
    G[a].pb(b), G[b ^ 1].pb(a ^ 1);
}
int fa[N], in[N], dep[N], top[N], son[N], zson[N], tim;
void dfs1(int u) {
    son[u] = 1;
    go(i, u, v)if(v ^ fa[u]) {
        dep[v] = dep[u] + 1, fa[v] = u;
        dfs1(v);
        son[u] += son[v];
        if(son[v] > son[zson[u]]) zson[u] = v;
    }
}
void dfs2(int u, int from) {
    top[u] = from, in[u] = ++tim;
    if(zson[u]) dfs2(zson[u], from);
    go(i, u, v)if(v ^ fa[u] && v ^ zson[u]) dfs2(v, v);
}
vector<int> path[N << 2];
int L[N << 2], R[N << 2];
#define Ls o << 1
#define Rs o << 1 | 1
void modify(int L, int R, int l, int r, int o, int id) {
    if(L <= l && r <= R) { path[o].pb(id); return; }
    int mid = l + r >> 1;
    if(L <= mid) modify(L, R, l, mid, Ls, id);
    if(R > mid)  modify(L, R, mid + 1, r, Rs, id);
}
void build(int l, int r, int o) {
    L[o] = ++ndc, ndc += path[o].size(), R[o] = ndc;
    if(o > 1) {
        lim(L[o] << 1 | 1, R[o >> 1] << 1 | 1);
        if(!path[o].empty()) {
            int x = path[o][0];
            lim(R[o >> 1] << 1, x ^ 1);
        }
    }
    for(int i = 0; i < path[o].size(); ++i) {
        int x = path[o][i];
        lim(L[o] + i << 1 | 1, x ^ 1);
        lim(L[o] + i + 1 << 1 | 1, L[o] + i << 1 | 1);
        if(i ^ 0) lim(L[o] + i - 1 << 1, x ^ 1);
    }
    if(l == r) return;
    int mid = l + r >> 1;
    build(l, mid, Ls);
    build(mid + 1, r, Rs);
}
void ins(int x, int y, int id) {
    for(; top[x] ^ top[y]; y = fa[top[y]]) {
        if(dep[top[x]] > dep[top[y]]) swap(x, y);
        modify(in[top[y]], in[y], 1, n, 1, id);
    }
    if(dep[x] > dep[y]) swap(x, y);
    if(x ^ y) modify(in[x] + 1, in[y], 1, n, 1, id);
}
int low[Nd], pre[Nd], st[Nd], scc[Nd], scc_cnt, tp;
void tarjan(int u) {
    low[u] = pre[u] = ++tim;st[++ tp] = u;
    for(auto v : G[u]) {
        if(!low[v]) {
            tarjan(v);
            Min(pre[u], pre[v]);
        }else if(!scc[v]) Min(pre[u], low[v]);
    }
    if(low[u] == pre[u] && ++scc_cnt)
    for(int x = -1; x ^ u;)
    scc[x = st[tp--]] = scc_cnt;
}
int main() {
    n = gi();ndc = n;
    rep(i, 1, n - 1) Add(gi(), gi());
    dep[1] = 1, dfs1(1), dfs2(1, 1);
    int m = gi();
    rep(i, 1, m) {
        int a = gi(), b = gi(), c = gi(), d = gi();
        ins(a, b, i << 1);
        ins(c, d, i << 1 | 1);
    }
    build(1, n, 1);
    tim = 0;
    for(int i = 1; i <= ndc * 2 + 1; ++i) if(!scc[i]) tarjan(i);
    rep(i, 1, m) {
        if(scc[i << 1] == scc[i << 1 | 1]) return puts("NO"), 0;
    }
    puts("YES");
    rep(i, 1, m) {
        puts(scc[i << 1] < scc[i << 1 | 1] ? "1" : "2");
    }
    return 0;
}

转载于:https://www.cnblogs.com/yqgAKIOI/p/10143265.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值