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;	
} 


你可能感兴趣的:(09-07 HDU_Steps5.1 并查集 HDU1829 HDU1325 HDU1598 HDU3461 HDU3635 HDU2473 HDU3172 HDU3038)