[CF687D]Dividing Kingdom II

题目

传送门 to luogu

思路

不难想到这样一种思路,每次将边权最大的一条边的两个端点分到不同集合内。

我们先将边排序,然后用并查集进行判断,就有了 O [ q m + q n α ( n ) + m log m ] \mathcal O[qm+qn\alpha(n)+m\log m] 的算法,显然不够好。

接下来这个想法就惊为天人了——由于并查集最多连接 n 1 n-1 条边,所以这 n 1 n-1 条边就足以确定并查集的结构。我们只存这些有用的边,最后我们把作为答案的那条边也存下来——这是为了便于代码实现。

用线段树来维护,每个区间都存储这至多 n n 条边,并且按照权值从大到小排序。区间合并可以依靠归并排序的思想,并且使用并查集检查,所以建树是 O [ n m α ( n ) ] \mathcal O[nm\alpha(n)] 的。

查询的时候仍然可以利用归并排序呢。所以复杂度就是 O [ q n α ( n ) log m ] \mathcal O[qn\alpha(n)\log m] 的。

总复杂度 O [ α n ( m + q log m ) ] \mathcal O[\alpha n(m+q\log m)] 。不过建树的复杂度跑不满。

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
inline int_ readint(){
	int_ a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
template < class T >
void getMax(T&a,const T&b){
	if(a < b) a = b;
}

const int MaxM = 1000005;
const int MaxN = 1005;

int n; // 点的数量
namespace UFS{
	int fa[MaxN], val[MaxN];
	void init(){
		for(int i=1; i<=n; ++i)
			fa[i] = i, val[i] = 0;
	}
	int find(int a){
		if(fa[a] == a) return a;
		int root = find(fa[a]);
		val[a] ^= val[fa[a]];
		return fa[a] = root;
	}
	int combine(int a,int b){
		int x = find(a), y = find(b);
		if(x == y) // already connected
			if(val[a]^val[b])
				return 1; // 什么也不发生
			else return 0; // odd cycle!
		fa[x] = y; // add an edge
		val[x] = val[a]^val[b]^1;
		return 2; // 连接了两个连通块
	}
};

struct Edge{
	int from, to, val;
	Edge(int F=0,int T=0,int V=0){
		from = F, to = T, val = V;
	}
} e[MaxM];

/** @brief 两个关于边的序列,合并为新序列 */
void hebing(vector< int > &v,
		vector< int > &v1,
		vector< int > &v2){
	int p = 0, len1 = v1.size();
	int q = 0, len2 = v2.size();
	int len = len1+len2; v.resize(len);
	while(p < len1 || q < len2){
		if(p < len1 && (q >= len2 ||
			e[v1[p]].val > e[v2[q]].val))
				v[p+q] = v1[p], ++ p;
		else v[p+q] = v2[q], ++ q;
	}
	UFS::init();
	int delta = 0; // 往前推一点点
	for(int i=0,llb; i+delta<len; ++i){
		v[i] = v[i+delta];
		llb = UFS::combine(e[v[i]].from,e[v[i]].to);
		if(llb == 0){ // 有奇环了
			len = i+1+delta;
			break; // 最多就到这里了
		}
		if(llb == 1) // 啥也没有,农民傻了
			++ delta, -- i;
	}
	v.resize(len -= delta);
}

int m; // 线段树的长度:边的数量
vector< int > v[MaxM<<1|1];

int __id(int l,int r){ return (l+r)|(l!=r); }
void build(int l=1,int r=m){
	if(l == r){
		v[__id(l,r)].resize(1);
		v[__id(l,r)][0] = l;
		return ;
	}
	int t = (l+r)>>1;
	build(l,t), build(t+1,r);
	hebing(v[__id(l,r)],
		v[__id(l,t)],v[__id(t+1,r)]);
}

void query(int ql,int qr,
		vector< int > &res,
		int l=1,int r=m){
	if(ql <= l && r <= qr){
		res = v[__id(l,r)]; return ;
	}
	int t = (l+r)>>1;
	if(qr <= t) return query(ql,qr,res,l,t);
	if(t < ql) return query(ql,qr,res,t+1,r);
	vector< int > tmp[2];
	query(ql,qr,tmp[0],l,t);
	query(ql,qr,tmp[1],t+1,r);
	hebing(res,tmp[0],tmp[1]);
}

vector< int > res;
int main(){
	n = readint(), m = readint();
	int q = readint();
	for(int i=1; i<=m; ++i){
		e[i].from = readint();
		e[i].to = readint();
		e[i].val = readint();
	}
	build();
	while(q --){
		int l = readint();
		int r = readint();
		query(l,r,res); UFS::init();
		bool notAny = true;
		for(int i=0,len=res.size(); i<len; ++i)
			if(UFS::combine(e[res[i]].from,
				e[res[i]].to) == 0)
					notAny = false;
		if(!notAny)
			printf("%d\n",e[res.back()].val);
		else printf("-1\n");
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/108068700