1. 程式人生 > >【CF687D】Dividing Kingdom II 線段樹+並查集

【CF687D】Dividing Kingdom II 線段樹+並查集

三種 for uil tro log std 從大到小 typedef 證明

【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

【CF687D】Dividing Kingdom II 線段樹+並查集