【CF687D】Dividing Kingdom II 线段树+并查集

【CF687D】Dividing Kingdom II

题意:给你一张n个点m条边的无向图,边有边权$w_i$。有q个询问,每次给出l r,问你:如果只保留编号在[l,r]中的边,你需要将所有点分成两个集合,使得这个划分的代价最小,问最小代价是什么。一个划分的代价是指,对于所有两端点在同一集合中的边,这些边的边权最大值。如果没有端点在同一集合中的边,则输出-1。

$n,q\le 1000,m\le \frac {n(n-1)} 2,w_i\le 10^9$

题解:先考虑暴力的做法,我们将所有边按权值从大到小排序,然后一个一个加到带权并查集里,标记两端点不在同一集合中,如果一条边的两端点已经在同一集合中,则输出答案。

但是问题在于边数非常大,不过仔细分析发现,我们可以将所有边按加入并查集时的情况分成如下三种:

1.如果a和b不在同一连通块内,我们连接这两个连通块,并标记a和b不在同一集合中。

2.如果a和b在同一连通块内,且a和b不在同一集合,则我们不用管。

3.如果a和b在同一连通块内,且a和b在同一集合,则输出答案。

我们令1和3这样的边为关键边。容易发现下面两条重要的引理:

引理1:关键边的数目不超过n条。

引理2:如果我们忽视非关键边,答案不变。

证明是显然的。但是这给我们一个非常重要的思路:如果我们预处理出区间内所有的关键边,则我们可以把每次查询的复杂度由O(m)变成O(n)!

进一步的,我们可以用以边的编号为下标的线段树来维护并查集。对于每个结点,我们已经处理完了它的左右两个子节点,其中每个节点都维护了该区间内的不超过n条关键边,我们只需要将左右两个节点的关键边归并起来,再用并查集处理一下即可。然后查询时,我们把所有线段树上的区间的一共$O(n\log n)$条关键边拿出来,一起处理一下即可。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
#define lson x<<1
#define rson x<<1|1
using namespace std;
const int maxn=1010;
const int maxm=500010;
typedef vector<int> vi;
int n,m,q;
vi s[maxm<<2];
vector<int>::iterator ia,ib;
int pa[maxm],pb[maxm],pc[maxm],f[maxn],g[maxn],p[maxm];
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	{if(gc==‘-‘)	f=-f;	gc=getchar();}
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+(gc^‘0‘),gc=getchar();
	return ret*f;
}
int find(int x)
{
	if(f[x]==x)	return x;
	int t=f[x];
	f[x]=find(t);
	g[x]^=g[t];
	return f[x];
}
inline vi merge(vi a,vi b)
{
	int i,cnt=0,x,y;
	vi c;
	for(ia=a.begin();ia!=a.end();ia++)	f[pa[*ia]]=pa[*ia],f[pb[*ia]]=pb[*ia],g[pa[*ia]]=g[pb[*ia]]=0;
	for(ib=b.begin();ib!=b.end();ib++)	f[pa[*ib]]=pa[*ib],f[pb[*ib]]=pb[*ib],g[pa[*ib]]=g[pb[*ib]]=0;
	for(ia=a.begin(),ib=b.begin();ia!=a.end()||ib!=b.end();)
	{
		if(ia!=a.end()&&(ib==b.end()||pc[*ia]>pc[*ib]))	p[++cnt]=*ia,ia++;
		else	p[++cnt]=*ib,ib++;
	}
	for(i=1;i<=cnt;i++)
	{
		x=pa[p[i]],y=pb[p[i]];
		if(find(x)!=find(y))	g[f[x]]=g[x]^g[y]^1,f[f[x]]=f[y],c.push_back(p[i]);
		else	if(g[x]!=g[y])	continue;
		else
		{
			c.push_back(p[i]);
			break;
		}
	}
	return c;
}
void build(int l,int r,int x)
{
	if(l==r)
	{
		s[x].push_back(l);
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,lson),build(mid+1,r,rson);
	s[x]=merge(s[lson],s[rson]);
}
vi query(int l,int r,int x,int a,int b)
{
	if(a<=l&&r<=b)	return s[x];
	int mid=(l+r)>>1;
	if(b<=mid)	return query(l,mid,lson,a,b);
	if(a>mid)	return query(mid+1,r,rson,a,b);
	return merge(query(l,mid,lson,a,b),query(mid+1,r,rson,a,b));
}
int main()
{
	//freopen("cf687D.in","r",stdin);
	n=rd(),m=rd(),q=rd();
	int i,a,b,x,y;
	vi t;
	for(i=1;i<=m;i++)	pa[i]=rd(),pb[i]=rd(),pc[i]=rd();
	build(1,m,1);
	for(i=1;i<=q;i++)
	{
		a=rd(),b=rd();
		t=query(1,m,1,a,b);
		for(ia=t.begin();ia!=t.end();ia++)	f[pa[*ia]]=pa[*ia],f[pb[*ia]]=pb[*ia];
		for(ia=t.begin();ia!=t.end();ia++)
		{
			x=pa[*ia],y=pb[*ia];
			if(find(x)==find(y))	break;
			f[f[x]]=f[y];
		}
		if(ia==t.end())	puts("-1");
		else	printf("%d\n",pc[*ia]);
	}
	return 0;
}//5 9 1 4 1 46 1 3 29 3 2 58 1 5 61 2 4 88 1 2 87 4 5 58 3 5 69 3 4 28 2 7

原文地址:https://www.cnblogs.com/CQzhangyu/p/8537443.html

时间: 2024-12-12 05:23:39

【CF687D】Dividing Kingdom II 线段树+并查集的相关文章

codeforces 687D Dividing Kingdom II 带权并查集(dsu)

题意:给你m条边,每条边有一个权值,每次询问只保留编号l到r的边,让你把这个图分成两部分 一个方案的耗费是当前符合条件的边的最大权值(符合条件的边指两段点都在一个部分),问你如何分,可以让耗费最小 分析:把当前l到r的边进行排序,从大到小,从大的开始不断加边,判断当前能否形成二分图,如果能形成二分图,继续加边 如果不能形成二分图,那当前边的权值就是最小耗费(是不是很眼熟) 思路很清晰,现在我们要解决的是如何判断可以形成二分图,有两种,一个是2染色当前图(肯定超时) 所以只剩一种方法,带权并查集

UVALive 4730 Kingdom 线段树+并查集

题目链接:点击打开链接 题意见白书P248 思路: 先把读入的y值都扩大2倍变成整数 然后离散化一下 用线段树来维护y轴 区间上每个点的 城市数量和联通块数量, 然后用并查集维护每个联通块及联通块的最大最小y值,还要加并查集的秩来记录每个联通块的点数 然后就是模拟搞.. T^T绝杀失败题..似乎数组开小了一点就过了,== #include<stdio.h> #include<math.h> #include<vector> #include<string.h>

UVA 1455 - Kingdom(线段树+并查集)

UVA 1455 - Kingdom 题目链接 题意:给定一些城市坐标点,连在一起的城市称为一个州,现在用两种操作,road表示把城市a,b建一条路,line表示询问一个y轴上穿过多少个州,和这些州共包含多少个城市 思路:利用并查集维护每个州的上界和下界还有城市个数,然后每次加进一条路的时候,根据两个集合的位置可以处理出区间的州和城市数如何进行加减,然后利用线段树搞就可以了 代码: #include <cstdio> #include <cstring> #include <

(线段树+并查集) Codeforces Round #416 (Div. 2) E Vladik and Entertaining Flags

In his spare time Vladik estimates beauty of the flags. Every flag could be represented as the matrix n?×?m which consists of positive integers. Let's define the beauty of the flag as number of components in its matrix. We call component a set of cel

【BZOJ-3673&amp;3674】可持久化并查集 可持久化线段树 + 并查集

3673: 可持久化并查集 by zky Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 1878  Solved: 846[Submit][Status][Discuss] Description n个集合 m个操作操作:1 a b 合并a,b所在集合2 k 回到第k次操作之后的状态(查询算作操作)3 a b 询问a,b是否属于同一集合,是则输出1否则输出0 0<n,m<=2*10^4 Input Output Sample Input 5 6

【BZOJ 4662】 4662: Snow (线段树+并查集)

4662: Snow Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 136  Solved: 47 Description 2333年的某一天,临冬突降大雪,主干道已经被雪覆盖不能使用.城 主 囧·雪 决定要对主干道进行一次清扫. 临冬城的主干道可以看为一条数轴.囧·雪 一共找来了n个清理工,第 i个清理工的工作范围为[li,ri],也就是说这个清理工会把[li,ri]这一 段主干道清理干净(当然已经被清理过的部分就被忽略了).当然有可能主 干道

UVALive 4730 线段树+并查集

点击打开链接 题意:在坐标上给n个点,r的操作是将两个点连起来,l的操作是问你y=u的这条线连接的集合块数和这些集合内的点的个数 思路:很麻烦的一道题,在网上看了题意和做法后,开始了一下午的调bug过程,做法很好懂,我开了两个线段树,一个维护点代表的直线的集合个数,另一个则是路过集合内的点的个数,然后集合的判断直接用并查集就行了,这是两个核心,然后就是自己瞎写的了,代码丑的可以而且好像除了本人别人看着可能要骂人了,有兴趣研究的可以留言我来解答,那难的部分其实就是并查集合并时该怎么将这两个要维护的

【Codeforces811E】Vladik and Entertaining Flags [线段树][并查集]

Vladik and Entertaining Flags Time Limit: 20 Sec  Memory Limit: 512 MB Description n * m的矩形,每个格子上有一个数字代表颜色. q次询问,询问[l, r]有几个连通块,若颜色相同并且连通则属于同一个连通块. Input 输入第一行n,m,q. 然后一个n*m的矩形. 之后q行,每行两个整数l,r. Output 输出q行,对于每个询问输出答案. Sample Input 4 5 4 1 1 1 1 1 1 2

【XSY2707】snow 线段树 并查集

题目描述 有\(n\)个人和一条长度为\(t\)的线段,每个人还有一个工作范围(是一个区间).最开始整条线段都是白的.定义每个人的工作长度是这个人的工作范围中白色部分的长度(会随着线段改变而改变).每一天开始时你要选择一个人满足这个人的工作长度最小(如果有多个就选编号最小的).把这个人的工作区间染黑.请你输出每天你选了哪个人. 保证工作范围中左端点和右端点单调递增. \(n\leq 300000\) 题解 先把线段离散化成很多个小区间,那么每个小区间只会被染黑一次(染黑之后不会变白). 因此每次