09-07 HDU_Steps5.1 並查集 HDU1829 HDU1325 HDU1598 HDU3461 HDU3635 HDU2473 HDU3172 HDU3038

HDU STEP 5.1 並查集


5.1.1 HDU1829 A Bug's Life

給出異性對a,b 判斷是否有衝突,即給出a,b異性,a,c異性,又給出b,c異性,顯然是衝突了(同性戀~)

用opp[x]數組表示與x對立的bug,(其實就是判斷一個二分圖)

輸入a,b後顯然分四種情況

1,opp[a]==-1,opp[b]==-1,a和b都沒有出現過,分別加到各自的對立集合 opp[a]=b opp[b]=a

2,opp[a]!=-1,opp[b]==-1,b沒有出現過,將其合併到a的對立集合裏,opp[b]=a,merge(b,opp[a])

3.opp[a]==1,opp[b]!=-1同上處理

4.opp[a]!=-1,opp[b]!=-1,a和b都出現過,分別合併到各自的對立集合,merge(opp[a],b),merge(opp[b],a)

#include <cstdio>
#include <cstdlib> 
using namespace std;
const int MAXN=2010;
//opp保存與a異性的bug 
int cas,n,m,a,b,yes,p[MAXN],opp[MAXN]; 
char c;
void init(){
	for(int i=1;i<=n;i++)p[i]=i,opp[i]=-1; 
	yes=1;
}
int find(int x){
	if(x!=p[x])p[x]=find(p[x]);
	return p[x];
}
void merge(int x,int y){
	p[find(x)]=find(y); 
}
inline void scan(int &x){
	while(c=getchar(),c<'0'||c>'9');x=c-'0';
	while(c=getchar(),c>='0'&&c<='9')x=x*10+c-'0';	
}
int main(){
	scan(cas);
	for(int i=1;i<=cas;i++){
		scan(n);scan(m); 
		init();
		while(m--){
			scan(a);scan(b);
			if(yes){
				//都沒有出現過 
				if(opp[a]==-1&&opp[b]==-1){
					opp[a]=b,opp[b]=a;	
				//出現過其中一個,將另一個與它的異性集合合併 
				}else if(opp[a]==-1){
					opp[a]=b;
					merge(a,opp[b]);
				}else if(opp[b]==-1){
					opp[b]=a;
					merge(b,opp[a]);
				//都出現過,先查找是否在一個集合中,若不在,和對方的異性集合合併 
				}else{
					if(find(a)==find(b))yes=0;
					else{
						merge(a,opp[b]);
						merge(b,opp[a]);
					}
				}
			}
		}
		printf("Scenario #%d:\n",i);
		printf(yes?"No suspicious bugs found!\n":"Suspicious bugs found!\n");
		printf("\n");	
	}
	return 0;	
} 

5.1.2 HDU1325 Is It A Tree?

這題寫了我很久..只能說我對樹的理解還不夠深刻..

三個條件,不能有環,不能是森林,每個點入度最大爲1(一直沒注意這種情況!!)

前兩個用並查集都可以判斷,入度用一個數組在讀取的時候保存

#include <cstdio>
#include <string.h> 
using namespace std;
const int MAXN=100005;
int par[MAXN],yes,hash[MAXN],in[MAXN];
void init(){
	yes=1;
	memset(hash,0,sizeof hash);
	memset(in,0,sizeof in); 
	for(int i=1;i<MAXN;i++)par[i]=i;	
} 
int find(int x){
	if(x!=par[x])par[x]=find(par[x]);
	return par[x];
}
void merge(int x,int y){
	par[find(x)]=find(y);
}
int main(){
	int a,b,cas=1,ta;
	init();
	while(scanf("%d%d",&a,&b)){
		if(a<0&&b<0)break;
		if(a==0&&b==0){
			if(yes){
				for(int i=1;i<MAXN;i++){if(hash[i]){ta=find(i);break;}}
				for(int i=1;i<MAXN;i++){
					//不能有點的入度爲2 
					if(in[i]>1){yes=0;break;}
					//不能是森林 
					if(hash[i]&&ta!=find(i)){yes=0;break;}
				}		
			}
			if(yes)printf("Case %d is a tree.\n",cas++);
			else printf("Case %d is not a tree.\n",cas++);
			init();
		}else{
			//不能生成環 
			if(find(a)==find(b))yes=0;
			if(!yes)continue;
			hash[a]=hash[b]=1;
			in[b]++; 
			merge(a,b);
		}
	}	
}

5.1.3 HDU1598 find the most comfortable road

找一條路使路上的最大值和最小值之差最小

並查集可以判連通,但是沒有想到這題也可以用並查集做..

將邊的權值從小到大排序,然後開始枚舉,依次添加比它大的邊,知道起點和終點連通爲止(find(u)==find(v)),取最小值作爲結果

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
struct sars{
	int u,v,w;
	bool operator <(const sars& s)const{return w<s.w;}
}s[1001];
int n,m,pa[201];

void init(){for(int i=0;i<201;i++)pa[i]=i;}
int find(int x){return x==pa[x]?x:pa[x]=find(pa[x]);}
void merge(int x,int y){x=find(x),y=find(y);if(x!=y)pa[x]=y;}

int solve(int st,int en){
	//判斷是否連通 
	init();
	for(int i=0;i<m;i++){
		merge(s[i].u,s[i].v);		
	} 	
	if(find(st)!=find(en))return -1;
	//以每條邊爲起始邊,依次添加權值比它大的邊,直到連通,連通邊-起始邊=差值 
	int res=1e9;
	for(int i=0;i<m;i++){
		init();
		for(int j=i;j<m;j++){
			if(s[j].w-s[i].w>=res)continue;
			merge(s[j].u,s[j].v);
			if(find(st)==find(en))res=min(res,s[j].w-s[i].w);	
		}		
	} 
	return res;
}
char c;
inline void scan(int &x){
	while(c=getchar(),c<'0'||c>'9');x=c-'0';
	while(c=getchar(),c>='0'&&c<='9')x=x*10+c-'0';	
}
int main(){
	while(scanf("%d%d",&n,&m)!=EOF){
		int a,b,c;
		for(int i=0;i<m;i++){
			scan(s[i].u);scan(s[i].v);scan(s[i].w);
		}
		//按權值排序 
		sort(s,s+m);
		scan(a);
		for(int i=0;i<a;i++){
			scan(b);scan(c);
			printf("%d\n",solve(b,c));
		}
	}
	return 0;	
} 


5.1.4 HDU3461 Code Lock

一道好題,不過思路比較難想

如果沒有區間存在,答案是26^n,每增加一個區間,n-1(這裏試一下就知道了,證明也很容易,因爲這個區間可以變成26種狀態~).但是要注意的是,比如已經有(1,10)和(1,3)在了,此時再增加(4,10)就沒有作用了..

使用並查集對於[l,r]我們將l,r+1兩個點並起,如果新線段的兩個點是同一個集合,就不用減了

#include <cstdio>
using namespace std;
const int MAXN=10000010;
const int PMOD=1000000007;
int n,m,a,b,ans;
int p[MAXN];
void init(){for(int i=1;i<=n+2;i++)p[i]=i;}
int find(int x){return x==p[x]?x:p[x]=find(p[x]);}
int merge(int x,int y){
	x=find(x),y=find(y);
	if(x==y)return 0;
	p[x]=y;
	return 1;
}
long long mi(int x){//二分求求26^x 
	if(x==0)return 1;
	long long a=mi(x/2);
	a=a*a%PMOD;
	if(x%2==1)a=a*26%PMOD;
	return a;
}
int main(){
	while(scanf("%d%d",&n,&m)!=EOF){
		init();
		ans=0;
		while(m--){
			/*
				每加一個線段就會x1
				不過注意線段劃分時會出現重複比如 [1,10],[1,3]
				這時候再來一個[4,10]就不用減了,因爲前兩個線段已經劃出這個了
				使用並查集對於[l,r]我們將l,r+1兩個點並起
				如果新線段的兩個點是同一個集合,就不用減了
			*/
			scanf("%d%d",&a,&b);
			ans+=merge(a,b+1);	
		}
		printf("%I64d\n",mi(n-ans));
	}	
}

5.1.5 HDU3635 Dragon Balls

很裸的並查集,用負節點標記集合元素個數,ts[x]代表x運送次數,在壓縮路徑很修改

#include <cstdio>
#include <cstdlib>
using namespace std;
const int MAXN=11000;
int cas,n,a,b,q,p[MAXN],ts[MAXN];
char c[3],cc;
void init(){
	for(int i=1;i<=n;i++){p[i]=-1;ts[i]=0;}	
}
int find(int x){
	if(p[x]<0)return x;
	int t=p[x];
	p[x]=find(p[x]);
	ts[x]+=ts[t]; 
	return p[x];
}
void merge(int x,int y){
	x=find(x),y=find(y);
	if(x!=y)p[y]+=p[x],p[x]=y,ts[x]=1;
}
inline void scan(int &x){
	while(cc=getchar(),cc<'0'||cc>'9');x=cc-'0';
	while(cc=getchar(),cc>='0'&&cc<='9')x=x*10+cc-'0';	
}
int main(){
	scan(cas);
	for(int ca=1;ca<=cas;ca++){ 
		printf("Case %d:\n",ca);
		scan(n);scan(q);
		init();
		while(q--){
			scanf("%s",c);
			if(c[0]=='T'){
				scan(a);scan(b); 
				merge(a,b);
			}else{
				scan(a);
				int tt=find(a);
				 
				printf("%d %d %d\n",tt,-p[tt],ts[a]);
			}	
		}
	} 
	//system("pause");
	return 0; 
} 

5.1.6 HDU2473 Junk-Mail Filter

好題啊..虛擬根節點,這樣在刪除時就不會影響到其它的節點了.

#include <cstdio>
#include <string.h>
using namespace std;
int n,m,a,b,cnt,p[2000000],s[1000041],ans;
char c;
inline void scan(int &x){
	while(c=getchar(),c<'0'||c>'9');x=c-'0';
	while(c=getchar(),c>='0'&&c<='9')x=x*10+c-'0';	
}
void init(){
	cnt=n*2,ans=0;
	for(int i=0;i<n;i++)p[i]=i+n;
	for(int i=n;i<n*2+m;i++)p[i]=i;
	memset(s,0,sizeof s);
}
int find(int x){return x==p[x]?x:p[x]=find(p[x]);}
void merge(int x,int y){p[find(x)]=find(y);}

//固定根節點,n+1~n+n作爲根節點,而1~n作爲虛擬根節點(指向n+1~n+n),之後在增添n+n~n+n+m作爲備用節點
//刪除時直接修改1~n指向的節點到n+n後的節點
int main(){
	int cas=1;
	while(scanf("%d%d",&n,&m),n||m){
		init();
		for(int mm=0;mm<m;mm++){
			scanf(" %c",&c);
			if(c=='M'){
				scan(a);scan(b);
				merge(a,b);	
			}else{
				scan(a);
				p[a]=cnt++;	
			}
		}
		for(int i=0;i<n;i++){if(s[find(i)]==0){ans++,s[find(i)]=1;}}
		printf("Case #%d: %d\n",cas++,ans);
	}
	return 0;	
}

5.1.7 HDU3172 Virtual Friends

裸並查集,給字符串寫個哈希函數就可以了


5.1.8 HDU3038 How Many Answers Are Wrong

壓根沒想到怎麼用並查集做..還是看了大牛的解題報告

和上面那一題Code Lock有點像,[1,10]和[1,4]確定了,[5,10]也就確定了

所以將每次端點合併,sum[x]表示tot[x]-tot[root](即第x+1個數到第root個數的和) tot[x]表示前x個數的和


#include <cstdio>
using namespace std;
const int MAXN=200010;
int n,m,ai,bi,si,wa;
int p[MAXN],sum[MAXN];
void init(){for(int i=0;i<=n;i++)p[i]=i,sum[i]=0;}
int find(int x){
	if(x!=p[x]){
		int t=p[x];
		p[x]=find(p[x]);	
		sum[x]+=sum[t]; 
	}
	return p[x];
}
bool merge(int x,int y,int c){
	int px=find(x),py=find(y);
	if(px==py)return sum[x]-sum[y]==c;
	//sum[x]表示tot[r]-tot[x],r=root; 
	p[px]=py;
	sum[px]=sum[y]-sum[x]+c;
	return true;
}

int main(){
	while(~scanf("%d%d",&n,&m)){
		wa=0;
		init();
		while(m--){
			scanf("%d%d%d",&ai,&bi,&si);
			if(!merge(ai-1,bi,si))wa++;	
		}
		printf("%d\n",wa);
	}
	return 0;	
} 


發佈了91 篇原創文章 · 獲贊 6 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章