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


    【CF687D】Dividing Kingdom II

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

    $n,qle 1000,mle frac {n(n-1)} 2,w_ile 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(nlog 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
    ",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
  • 相关阅读:
    【转载】数据结构与算法设计
    【转载】简述Linux的启动过程
    【转载】20分钟MySQL基础入门
    【转载】linux内核笔记之进程地址空间
    【转载】linux内核笔记之高端内存映射
    Logical Address->Linear Address->Physical Address
    【转载】教你分分钟学会用python爬虫框架Scrapy爬取心目中的女神
    【转载】不会编程也能写爬虫?可视化爬虫工具是什么东东
    【转载】我是一个键盘
    80. Remove Duplicates from Sorted Array II
  • 原文地址:https://www.cnblogs.com/CQzhangyu/p/8537443.html
Copyright © 2020-2023  润新知