1. 程式人生 > >[UOJ#407/LOJ#2865][IOI2018]狼人(Kruskal 重構樹 + 倍增 + 主席樹)

[UOJ#407/LOJ#2865][IOI2018]狼人(Kruskal 重構樹 + 倍增 + 主席樹)

Address

BZOJ4899
UOJ#407
LOJ#2865

Solution

  • 先考慮轉化一下問題
  • 詢問四個引數 S S E E
    L L R R
  • 等價於一個圖 G
    1 G_1
    ,它只包含原圖中節點編號 L \ge L 的點和這些點之間的邊
  • 另一個圖 G
    2 G_2
    ,只包含原圖中節點編號 R \le R 的點和這些點之間的邊
  • 詢問是否存在一個點 u u
  • 滿足點 u u 既在 G 1 G_1 S S 所在的連通塊內
  • 又在 G 2 G_2 E E 所在的連通塊內
  • Kruskal 重構樹可以在圖持續加邊時維護歷史版本的連通塊資訊
  • 即把連通塊對映成子樹
  • 基本思想就是合併兩個連通塊時,新建一個點 u u
  • 讓這兩個連通塊對應子樹的根作為 u u 的子節點
  • 而如果要找到點 u u 在第 k k 次連邊之後所在的連通塊
  • 就只需要找到點 u u 的祖先中,深度最小且連通時間 k \le k 的點
  • 可以使用樹上倍增實現
  • 回到原問題
  • 發現一個小問題:需要維護連通次序的是點而不是邊
  • 但其實只需要令歷史版本 i i 表示編號 i \le i i \ge i )的點以及它們之間連的邊即可
  • 建立兩棵重構樹 T 1 T_1 T 2 T_2
  • T 1 T_1 的歷史版本 i i 表示編號 i \le i 的點以及它們之間連的邊
  • T 2 T_2 的歷史版本 i i 表示編號 i \ge i 的點以及它們之間連的邊
  • 問題轉化為是否存在一個點 u u
  • 既在 T 1 T_1 的某個點的子樹內
  • 又在 T 2 T_2 的某個點的子樹內
  • 即求 T 1 T_1 的 DFS 序列的某個區間內,是否存在一個點 u u
  • 使得 u u T 2 T_2 中的 DFS 序在某個區間內
  • 主席樹實現
  • 複雜度 O ( ( N + M + Q ) log ( N + M + Q ) ) O((N+M+Q)\log (N+M+Q))

Code

  • 完整程式 in luogu
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
#define Tree1(u) for (int e = adj1[u], v = go1[e]; e; e = nxt1[e], v = go1[e])
#define Tree2(u) for (int e = adj2[u], v = go2[e]; e; e = nxt2[e], v = go2[e])

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

const int N = 2e5 + 5, Z = N << 1, M = N << 2, E = N * 3, LogN = 21,
L = 1e7 + 5;

int n, m, q, ecnt, nxt[M], adj[N], go[M], Xin[Z], Yin[Z], ToTin, ToTde,
Xde[Z], Yde[Z], maxin[E], maxde[E], fa[E], ancin[E][LogN], ancde[E][LogN],
Tin, Tde, ecnt1, nxt1[E], adj1[E], go1[E], ecnt2, nxt2[E], adj2[E], go2[E],
dfnin[E], dfnde[E], szein[E], szede[E], rt[E], ToT;

struct node
{
	int lc, rc, sum;
} T[L];

void ins(int y, int &x, int l, int r, int p)
{
	T[x = ++ToT] = T[y]; T[x].sum++;
	if (l == r) return;
	int mid = l + r >> 1;
	if (p <= mid) ins(T[y].lc, T[x].lc, l, mid, p);
	else ins(T[y].rc, T[x].rc, mid + 1, r, p);
}

void emptys(int y, int &x)
{
	T[x = ++ToT] = T[y];
}

int sumorz(int y, int x, int l, int r, int s, int e)
{
	if (l == s && r == e) return T[x].sum - T[y].sum;
	int mid = l + r >> 1;
	if (e <= mid) return sumorz(T[y].lc, T[x].lc, l, mid, s, e);
	else if (s >= mid + 1) return sumorz(T[y].rc, T[x].rc, mid + 1, r, s, e);
	else return sumorz(T[y].lc, T[x].lc, l, mid, s, mid)
		+ sumorz(T[y].rc, T[x].rc, mid + 1, r, mid + 1, e);
}

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}

void add_edge1(int u, int v)
{
	nxt1[++ecnt1] = adj1[u]; adj1[u] = ecnt1; go1[ecnt1] = v;
}

void add_edge2(int u, int v)
{
	nxt2[++ecnt2] = adj2[u]; adj2[u] = ecnt2; go2[ecnt2] = v;
}

int cx(int x)
{
	if (fa[x] != x) fa[x] = cx(fa[x]);
	return fa[x];
}

void zm(int x, int y)
{
	int ix = cx(x), iy = cx(y);
	if (ix != iy) fa[iy] = ix;
}

void dfsin(int u)
{
	dfnin[u] = ++Tin;
	szein[u] = 1;
	Tree1(u) dfsin(v), szein[u] += szein[v];
}

void dfsde(int u)
{
	dfnde[u] = ++Tde;
	if (u <= n) ins(rt[Tde - 1], rt[Tde], 1, Tin, dfnin[u]);
	else emptys(rt[Tde - 1], rt[Tde]);
	szede[u] = 1;
	Tree2(u) dfsde(v), szede[u] += szede[v];
}

int findin(int u, int x)
{
	int i;
	Rof (i, 20, 0)
		if (ancin[u][i] && maxin[ancin[u][i]] <= x)
			u = ancin[u][i];
	return u;
}

int findde(int u, int x)
{
	int i;
	Rof (i, 20, 0)
		if (ancde[u][i] && maxde[ancde[u][i]] >= x)
			u = ancde[