[BZOJ4025] 二分图(线段树分治+可撤销并查集)

题意

  • 给你 n n n个点, m m m条边,每条边有一个出现时间和一个消失时间,求出每一个时刻当前图是否为二分图( n ≤ 1 0 5 , m ≤ 2 × 1 0 5 n\le10^5,m\le2\times10^5 n105,m2×105)。

感谢Inspector_Javert的这篇博客,让我看懂了什么是线段树分治。

首先我们要知道如何判定一个图是不是二分图,那就是这个图不存在奇环。然后我们可以以时间为轴建立线段树,把每条边放到线段树中,由线段树的性质我们可以知道每条边最多出现 log ⁡ n \log n logn次,然后我们深搜这棵时间线段树,每次把路径上的所有节点上的所有边全部加入,然后判定当前图是不是二分图,那么现在问题就被我们转化成了给定一些边(支持删除)的二分图判定问题,我们考虑来解决这个问题。

我们可以用一个按秩合并的带权并查集来实现,每个点记录到父亲的树上距离,每次在连通图上加边时判定路径长度奇偶性即可,每次加入一条边 ( x , y ) (x,y) (x,y)时,设 x x x在并查集的根节点为 u u u y y y在并查集的根节点为 v v v,那么 u u u v v v的距离就是 u − x − y − v u-x-y-v uxyv的距离,这个东西暴力查找就好了,支持删除也很简单,因为这里的边都是按顺序加入的,删除的时候也只会删除最后加入的几条边,我们用一个栈把每次修改的信息存下来,删除的时候把栈中的信息还原即可,复杂度 O ( n log 2 n ) O(\text{n log}^2\text{n}) O(n log2n)

#include 

#define x first
#define y second
#define pb push_back
#define mp make_pair
#define inf (0x3f3f3f3f)
#define mem(a, b) memset(a, b, sizeof(a))
#define Rep(i, a) for (int i = 0; i < a; ++ i)
#define For(i, a, b) for (int i = a; i <= b; ++ i)
#define Forr(i, a, b) for (int i = a; i >= b; -- i)
#define Travel(i, x) for (int i = head[x]; i; i = nxt[i])

using namespace std;

typedef long long ll;
typedef pair<int, int> PII;

template<class T>inline T read(T &_) {
	T __ = getchar(), ___ = 1; _ = 0;
	for (; !isdigit(__); __ = getchar()) if (__ == '-') ___ = -1;
	for (; isdigit(__); __ = getchar()) _ = (_ << 3) + (_ << 1) + (__ ^ 48);
	return _ *= ___;
}

template<class T>inline bool chkmax(T &_, T __) { return _ < __ ? _ = __, 1 : 0; }
template<class T>inline bool chkmin(T &_, T __) { return _ > __ ? _ = __, 1 : 0; }

inline void proStatus() {
	ifstream t("/proc/self/status");
	cout << string(istreambuf_iterator<char>(t), istreambuf_iterator<char>());
}

const int N = 1e5 + 7;

int n, m, q, ans[N]; 

struct Union_Find_Set {

	struct node {
		int x, y, d;
	} S[N];

	int fa[N], dep[N], dis[N], top;

	Union_Find_Set() { Rep(i, N) fa[i] = i; }

	int find(int x) { return x == fa[x] ? x : find(fa[x]); }

	int getdis(int x) { return x == fa[x] ? dis[x] : dis[x] + getdis(fa[x]); }

	bool merge(int x, int y, int &cnt) {
		int ax = find(x), ay = find(y);
		if (ax ^ ay) {
			S[++ top] = (node) { ax, ay, dis[ax] }, ++ cnt;
			dis[ax] = getdis(x) + getdis(y) + 1, fa[ax] = ay;
			if (dep[ax] == dep[ay]) ++ dep[ay];
		} else {
			int disx = getdis(x), disy = getdis(y);
			if ((disx + disy + 1) & 1) return false;
		}
		return true;
	}

	void recall() {
		node now = S[top --];
		if (dep[now.x] == dep[now.y] - 1) 
			-- dep[now.y];
		fa[now.x] = now.x, dis[now.x] = now.d;
	}

} Set; 

struct Segment_Tree {

#define ls (bh << 1)
#define rs (ls | 1)
#define mid ((l + r) >> 1)
#define lson ls, l, mid
#define rson rs, mid + 1, r

	vector<PII> S[N << 2];

	void update(int bh, int l, int r, int x, int y, PII z) {
		if (x <= l && r <= y) S[bh].pb(z);
		else {
			if (x <= mid) update(lson, x, y, z);
			if (y > mid) update(rson, x, y, z);
		}
	}

	void Solve(int bh, int l, int r) {
		int sz = S[bh].size(), flag = 1, cnt = 0;
		Rep(i, sz) flag &= Set.merge(S[bh][i].x, S[bh][i].y, cnt);
		if (l == r) ans[l] = flag;
		else if(flag) Solve(lson), Solve(rson);
		Rep(i, cnt) Set.recall();
	}

} T; 

int main() {

	//freopen("4025.in", "r", stdin);
	//freopen("4025.out", "w", stdout);

	int x, y, s, t;

	read(n), read(m), read(q);
	For(i, 1, m) {
		read(x), read(y), read(s), read(t);
		T.update(1, 1, q, s + 1, t, mp(x, y));
	}
	T.Solve(1, 1, q);
	For(i, 1, q) 
		puts(ans[i] ? "Yes" : "No");

	return 0;
}

你可能感兴趣的:(线段树分治,并查集)