@loj - 2865@ 「IOI2018」狼人


@description@

在日本的茨城縣內共有 N 個城市和 M 條道路。這些城市是根據人口數量的升序排列的,依次編號爲 0 到 N-1。每條道路鏈接兩個不一樣的城市,而且能夠雙向通行。由這些道路,你能從任意一個城市到另外任意一個城市。ios

你計劃了 Q 個行程,這些行程分別編號爲 0 至 Q-1。第 i(0 <= i <= Q - 1)個行程是從城市 Si 到城市 Ei。算法

你是一個狼人。你有兩種形態:人形和狼形。在每一個行程開始的時候,你是人形。在每一個行程結束的時候,你必須是狼形。在行程中,你必需要變身(從人形變成狼形)剛好一次,並且只能在某個城市內(包括多是在 Si 或 Ei 內)變身。數組

狼人的生活並不容易。當你是人形時,你必須避開人少的城市,而當你是狼形時,你必須避開人多的城市。對於每一次行程 i(0 <= i <= Q - 1),都有兩個閾值 Li 和 Ri(0 <= Li <= Ri <= N - 1),用以表示哪些城市必需要避開。
準確地說,當你是人形時,你必須避開城市 0, 1, ..., Li - 1;而當你是狼形時,則必須避開城市 Ri + 1, Ri + 2, ..., N - 1。
這就是說,在行程 i 中,你必須在城市 Li, Li+1, ..., Ri 中的其中一個城市內變身。數據結構

你的任務是,對每一次行程,斷定是否有可能在知足上述限制的前提下,由城市 Si 走到城市 Ei。你的路線能夠有任意長度。函數

輸入格式
你須要實現下面的函數:ui

int[] check_validity(int N, int[] X, int[] Y, int[] S, int[] E, int[] L, int[] R)spa

N:城市的數量
X 和 Y:兩個長度爲 M 的數組。對於每一個 j(0 <= j <= M - 1),城市 X[j] 都有道路直接連到城市 Y[j]。
S, E, L, 及 R:均爲長度爲 Q 的數組,以表示行程。code

數據範圍與提示
2 <= N <= 200000, N - 1 <= M <= 400000, 1 <= Q <= 200000。
0 <= Li <= Si <= N - 1, 0 <= Ei <= Ri <= N - 1, Si ≠ Ei, Li <= Ri。
保證沒有重邊自環,保證圖連通。ip

@solution@

等價的題意:
從 S 出發只通過 L ~ N-1 的點可以到達的點集,與從 E 出發只通過 0 ~ R 的點可以到達的點集,兩個集合是否有交集。

現先考慮從 E 出發只通過 0 ~ R 的點可以到達的點。
若是從點 0 到點 N - 1 依次加入它們向前連的邊,並用相似於可持久化並查集的東西維護,只要調用第 R 個版本的並查集就能夠獲得 E 能到達的點。
可持久化並查集?有沒有想起 NOI2018 的 D1T1?是否對於這道題咱們也能夠轉成 kruskal 重構樹來作?

考慮對於全部邊,記邊權爲 min{它鏈接的兩個端點的編號},則只通過 0 ~ R 的點等價於只通過邊權 <= R 的邊。
因而一個點可以到達的點集,經過倍增能夠找到 kruskal 重構樹中對應的子樹,子樹內的全部點都是可到達的。
從 S 出發只通過 L ~ N-1 的點可以到達的點集能夠類比着建另外一棵樹。

經過 dfs 序,咱們能夠把問題轉化爲:
是否存在一個 (dfn1[x], dfn2[x]),使得 a <= dfn1[x] <= b, c <= dfn2[x] <= d。
經典的二維偏序問題,至關於統計這個二維區域的點數。

懶得動腦子爲了強化代碼能力打了個可持久化線段樹。
時間複雜度 O(nlogn)。

@accepted code@

#include "werewolf.h"
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define vi vector<int>
#define pb push_back
const int MAXN = 200000;
const int MAXQ = 200000;
const int MAXM = 400000;
int N, M, Q;
struct Graph{
    bool type;
    struct edge{
        int to; edge *nxt;
    }edges[2*MAXN + 5], *adj[2*MAXN + 5], *ecnt;
    int v[2*MAXN + 5], fa[20][2*MAXN + 5];
    Graph(int p) {ecnt = edges, type = p;}
    void addedge(int u, int v) {
        edge *p = (++ecnt);
        p->to = v, p->nxt = adj[u], adj[u] = p;
//      printf("! %d %d\n", u, v);
    }
    void dfs(int x, int f) {
        fa[0][x] = f;
        for(int i=1;i<20;i++)
            fa[i][x] = fa[i-1][fa[i-1][x]];
        for(edge *p=adj[x];p;p=p->nxt)
            dfs(p->to, x);
    }
    void build(int x) {v[0] = type ? (MAXN + 5) : 0; dfs(x, 0);}
    int query(int x, int k) {
        for(int i=19;i>=0;i--)
            if( type ) {
                if( v[fa[i][x]] <= k )
                    x = fa[i][x];
            }
            else {
                if( v[fa[i][x]] >= k )
                    x = fa[i][x];
            }
        return x;
    }
}G1(0), G2(1);
int fa[2*MAXN + 5];
int find(int x) {
    return fa[x] = (fa[x] == x ? x : find(fa[x]));
}
vi e[MAXN + 5];
void build1() {
    int cnt = N;
    for(int i=1;i<(N<<1);i++) fa[i] = i;
    for(int i=N;i>=1;i--) {
        G1.v[i] = i;
        for(int j=0;j<(int)e[i].size();j++)
            if( e[i][j] > i ) {
                int p = e[i][j], f = find(i), g = find(p);
                if( f != g ) {
                    int x = (++cnt);
                    G1.addedge(x, f), G1.addedge(x, g);
                    fa[f] = fa[g] = x;
                    G1.v[x] = i;
                }
            }
    }
    G1.build((N<<1)-1);
}
void build2() {
    int cnt = N;
    for(int i=1;i<(N<<1);i++) fa[i] = i;
    for(int i=1;i<=N;i++) {
        G2.v[i] = i;
        for(int j=0;j<(int)e[i].size();j++)
            if( e[i][j] < i ) {
                int p = e[i][j], f = find(i), g = find(p);
                if( f != g ) {
                    int x = (++cnt);
                    G2.addedge(x, f), G2.addedge(x, g);
                    fa[f] = fa[g] = x;
                    G2.v[x] = i;
                }
            }
    }
    G2.build((N<<1)-1);
}
struct segtree{
    struct node{
        int cnt; node *ch[2];
    }pl[20*MAXN + 5], *NIL, *ncnt;
    segtree() {
        NIL = ncnt = pl;
        NIL->ch[0] = NIL->ch[1] = NIL;
        NIL->cnt = 0;
    }
    node *insert(node *pre, int l, int r, int ps) {
        node *p = (++ncnt); (*p) = (*pre); p->cnt++;
        if( l == r ) return p;
        int mid = (l + r) >> 1;
        if( ps <= mid )
            p->ch[0] = insert(pre->ch[0], l, mid, ps);
        else p->ch[1] = insert(pre->ch[1], mid + 1, r, ps);
        return p;
    }
    int query(node *L, node *R, int l, int r, int ql, int qr) {
        if( ql > r || qr < l ) return 0;
        if( ql <= l && r <= qr ) return R->cnt - L->cnt;
        int mid = (l + r) >> 1;
        return query(L->ch[0], R->ch[0], l, mid, ql, qr) + query(L->ch[1], R->ch[1], mid + 1, r, ql, qr);
    }
}T;
segtree::node *rt[MAXN + 5];
int tid[2][MAXN + 5], dfn[2][MAXN + 5], dcnt[2];
int le[2][2*MAXN + 5], ri[2][2*MAXN + 5];
void dfs(const Graph &G, int x, int t) {
    if( x <= N ) {
        dfn[t][le[t][x] = ri[t][x] = tid[t][x] = (++dcnt[t])] = x;
    }
    else {
        le[t][x] = dcnt[t] + 1;
        for(Graph::edge *p=G.adj[x];p;p=p->nxt)
            dfs(G, p->to, t);
        ri[t][x] = dcnt[t];
    }
}
void get() {
    build1();
    build2();
    dfs(G1, (N<<1)-1, 0), dfs(G2, (N<<1)-1, 1);
    rt[0] = T.NIL;
    for(int i=1;i<=N;i++)
        rt[i] = T.insert(rt[i-1], 1, N, tid[1][dfn[0][i]]);
}
bool query(int s, int e, int l, int r) {
    int x = G1.query(s, l), y = G2.query(e, r);
    return T.query(rt[le[0][x]-1], rt[ri[0][x]], 1, N, le[1][y], ri[1][y]);
}
vi check_validity(int _N, vi X, vi Y, vi S, vi E, vi L, vi R) {
    N = _N, Q = S.size(), M = X.size();
    for(int i=0;i<M;i++)
        e[X[i]+1].pb(Y[i]+1), e[Y[i]+1].pb(X[i]+1);
    get(); vi A(Q);
    for(int i=0;i<Q;i++)
        A[i] = query(S[i]+1, E[i]+1, L[i]+1, R[i]+1);
    return A;
}
/*
S -> L ~ N (L <= S)
E -> 1 ~ R (E <= R)
*/

@details@

對於 kruskal 重構樹,其實我更願意將其看做 「可持久化並查集」。
由於它的做用就是將某時刻 t 以前的加邊操做執行後,圖連通的狀況。

可是某種意義上來講,kruskal 重構樹的思想更相似於點分樹之類的東西。即用數據結構再現某一算法的過程。

無論怎麼說,會用就行。
另外,比狠人還多一點——是個狼人。

相關文章
相關標籤/搜索