Graph theory 0x00【图论初丁】

拓扑排序

性质

给一张图,图中的点之间有先后关系,把这些先后关系转化为图中的边。直到入度为0的时候才放进BFS队列中,得到的便是拓扑排序。

题目

POJ 1270 Following the order(DFS+拓扑排序+字典序输出全部可能)

#include<iostream>
#include<stdio.h>
#include<set>
#include<cstdio>
#include<string.h>
#include<cstdlib>  
#include<stack>  
#include<queue>  
#include<algorithm>  
#include<cstring>  
#include<string>  
#include<cmath>  
#include<vector>  
#include<bitset>  
#include<list>  
#include<sstream>   
#include<map>
#include<functional> 
using namespace std;
const int N=28;
int exi[N],flag[N];
int vis[N];
vector<int>G[N];
int ans[N];
int n,m;
void solve(int u,int len){
	ans[len]=u;
	if(len==n-1){
		for(int i=0;i<=len;++i) printf("%c",'a'+ans[i]);
		printf("\n");
		return;
	}
	for(int i=0;i<G[u].size();++i)
		flag[G[u][i]]--;
	for(int i=0;i<26;++i){          //   全部试一遍 
		if(vis[i]) continue;
		if(exi[i]==0) continue;
		if(flag[i]) continue;
		vis[i]=1;
		solve(i,len+1);
		vis[i]=0;
	}
	for(int i=0;i<G[u].size();++i)
		flag[G[u][i]]++;
}
char s[N*2];
int main(){
	int cnt=0;
	while(gets(s)){
		if(cnt) printf("\n");cnt++;
		memset(flag,0,sizeof(flag));
		memset(vis,0,sizeof(vis));
		memset(exi,0,sizeof(exi));
		int len=strlen(s); 
		n=0;
		for(int i=0;i<len;++i){
			if(s[i]>='a'&&s[i]<='z'){
				n++;
				exi[s[i]-'a']=1;
			}
		}
		gets(s);
		len=strlen(s);
		int t=1;char x;
		for(int i=0;i<len;++i){
			if(s[i]>='a'&&s[i]<='z'){
				if(t){
					x=s[i];
				}else{
					G[x-'a'].push_back(s[i]-'a');
					flag[s[i]-'a']++;
				}
				t^=1;
			}
		}
		for(int i=0;i<26;++i){
			if(exi[i]==0) continue;
			if(flag[i]==0){
				vis[i]=1;
				solve(i,0);
				vis[i]=0;
			}
		}
		for(int i=0;i<N;++i)
			G[i].clear();
	}
}

HDU 3342(判断有无环)

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=104;
vector<int>G[N];
int flag[N];
bool solve(){
	int cnt=0;
	queue<int>q; 
	for(int i=0;i<n;++i)
		if(flag[i]==0) q.push(i);
	while(!q.empty()){
		int u=q.front();q.pop();
		cnt++;
		for(auto i:G[u]){
			flag[i]--;
			if(flag[i]==0){
				q.push(i);
			}
		}
	} 
	return cnt==n;
}
int main(){
	while(~scanf("%d%d",&n,&m)){
		if(n==0) return 0;
		for(int i=1;i<=m;++i){
			int x,y;
			scanf("%d%d",&x,&y);
			G[x].push_back(y);
			flag[y]++;
		}
		bool ans=solve();
		if(ans) printf("YES\n");
		else printf("NO\n");
		memset(flag,0,sizeof(flag));
		for(int i=0;i<n;++i) G[i].clear();
	}
}

HDU 2647 反向建图+拓扑排序(唯一)+判断有无环

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=10004;
vector<int>G[N];
int flag[N],ans[N];
int solve(){
	queue<int>q;
	int cnt=0;
	for(int i=1;i<=n;++i)
		if(flag[i]==0){
			q.push(i);
			ans[i]=888;
		}
	while(!q.empty()){
		int u=q.front();q.pop();cnt++;
		for(auto i:G[u]){
			flag[i]--;
			if(flag[i]==0){
				q.push(i);
				ans[i]=ans[u]+1;
			}
		}
	}
	if(cnt!=n) return -1;
	int res=0;
	for(int i=1;i<=n;++i)
		res+=ans[i];
	return res;
}
int main(){
	while(~scanf("%d%d",&n,&m)){
		for(int i=1;i<=m;++i){
			int x,y;
			scanf("%d%d",&x,&y);
			flag[x]++;
			G[y].push_back(x);
		}
		int res=solve();
		printf("%d\n",res);
		for(int i=1;i<=n;++i){
			ans[i]=0;
			flag[i]=0;
			G[i].clear();
		}
	}
}

HDU 5695 拓扑排序
因为要算入“包括自己在内的前方所有同学的最小ID”最大,那么就尽量从大到小排序。所以用优先队列。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int flag[N];
vector<int>G[N];
int n;
typedef long long ll;
int ans[N];
ll solve(){
	priority_queue<int>q;
	for(int i=1;i<=n;++i)
		if(flag[i]==0) q.push(i);
	int mi=N;
	ll ans=0;
	while(!q.empty()){
		int u=q.top();
		mi=min(mi,u);
		ans+=mi;
		q.pop();
		for(auto i:G[u])
			if(--flag[i]==0)
				q.push(i);
	}
	return ans;
}
void init(){
	memset(flag,0,sizeof(flag));
	for(int i=1;i<=n;++i)
		G[i].clear();
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int m;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=m;++i){
			int x,y;
			scanf("%d%d",&x,&y);
			G[x].push_back(y);
			flag[y]++;
		}
		ll res=solve();
		printf("%lld\n",res);
		init();
	}
}

HDU 4857 反向建图+优先队列
题目要一种非字典序的排序最小。
即先使得1最前,然后再考虑使得2最前,…,以此类推
我们考虑一张图(左)
图
对于2->6和5->3这两条同样阶级的路径,我们不知道该2先放,还是5先放。因为我们不知道哪条路径会先引出1.
但是对于3->1->7和3->6->4这两条同样阶级的路径,我们能够确定7一定放在4之后。因为他们两个作为路径的末尾,对引出数字已经没有影响,则只需考虑小的数字一定是越前越好即可(贪心)。
那么我们就把图反建(右),然后用优先队列先输出大的数字,存在数组中,最后反向输出数组即可。

#include<bits/stdc++.h>
using namespace std;
int n;
const int N=30005;
int flag[N],ans[N];
vector<int>G[N];
void solve(){
	priority_queue<int>q;
	for(int i=1;i<=n;++i)
		if(flag[i]==0) q.push(i);
	int cnt=0;
	while(!q.empty()){
		int u=q.top();q.pop();
		ans[++cnt]=u;
		for(auto i:G[u])
			if(--flag[i]==0)
				q.push(i);
	}
	for(int i=cnt;i>=1;--i)
		if(i!=1) printf("%d ",ans[i]);
		else printf("%d\n",ans[i]);
}
void init(){
	memset(flag,0,sizeof(flag));
	for(int i=1;i<=n;++i)
		G[i].clear();
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int m;
		scanf("%d%d",&n,&m);
		init();
		for(int i=1;i<=m;++i){
			int x,y;
			scanf("%d%d",&x,&y);
			G[y].push_back(x);
			flag[x]++;
		}
		solve(); 
	}
}

HDU 1811 并查集+拓扑排序

#include<bits/stdc++.h>
using namespace std;
int n;
const int N=1e4+4;
/*
如果A>B,我们find(A),find(B)
然后把fa[A].push_back(fa[B])
小于同理
如果A==B,我们find(A),find(B)
然后把fa[A]和fa[B]unite。
怎么unite呢?把fa[fa[A]]=fa[B]
然后把fa[A]内的全部弹出装进fa[B]中
把fa[A]的flg赋值给fa[B] 把点个数加上 

然后怎么判断结果?
遍历所有的点,从同时是一个连通块的根且flg==0的点放进queue
像之前那样 进行BFS
弹出可到达的点 find一下 减边 如果入度为0就push
对每一个正在处理的点 加上这个连通块的点个数 
那么 最后看个数 如果!=n 则有环:conflict。

信息完全:一条链 只有一种可能结果 那么queue里应该随时只有一个点
不然的话:说明有多种可能结果:uncertain 
*/ 
int fa[N],flg[N],sum[N];
queue<int>G[N];    //存图 
int find(int x){
	return fa[x]=fa[x]==x?x:find(fa[x]);
}
void unite(int x,int y){
	x=find(x);y=find(y);
	if(x==y) return;
	fa[x]=y;
	while(!G[x].empty()){
		G[y].push(G[x].front());G[x].pop();
	}
	flg[y]+=flg[x];
	sum[y]+=sum[x];
}
int solve(){
	queue<int>q;
	int cnt=0,cnt2=0;
	for(int i=0;i<n;++i)
		if(fa[i]==i&&flg[i]==0)
			q.push(i); 
	int num=0,flag=0;
	while(!q.empty()){
		if(q.size()>1) flag=1;         //即有多解:uncertain 
		int u=q.front();q.pop();
		num+=sum[u]; 
		while(!G[u].empty()){
			int i=G[u].front();G[u].pop();
			i=find(i);
			if(--flg[i]==0)
				q.push(i);
		}
	} 
	if(num!=n) return 0;       //conflict
	if(!flag) return 1;          //ok
	else return 2;             //uncertain
}
void init(){
	for(int i=0;i<n;++i){
		fa[i]=i;
		sum[i]=1;
		flg[i]=0;
		while(!G[i].empty()){
			G[i].pop();
		}
	}
}
int main(){
	int m;
	while(~scanf("%d%d",&n,&m)){
		init();
		for(int i=1;i<=m;++i){
			int x,y;char str[2];
			scanf("%d%s%d",&x,str,&y);
			if(str[0]=='='){
				unite(x,y);
			}else if(str[0]=='>'){
				x=find(x);y=find(y);
				G[x].push(y);
				flg[y]++;
			}else{
				x=find(x);y=find(y);
				G[y].push(x);
				flg[x]++;
			}
		}
		int ans=solve();
		if(ans==1) puts("OK");
		else if(ans==0) puts("CONFLICT");
		else puts("UNCERTAIN");
	}
}

欧拉路

性质

用一条路线覆盖所有边和点,则是欧拉路。

  • 判断是否欧拉路/回路
  • 输出路径(DFS回溯的时候才记录答案)

题目

POJ 1780 手动写栈模拟DFS
每一个长度n-1的状态视为一个点,加上一个0~9的数字转移到另一个长度为n-1的状态,这个过程视为一个边。求最短的包括所有长度为n的序列,则等于求欧拉路。

#include<cstdio>
const int N=1e5+10;
int node[N],stack[10*N];
char ans[10*N];
int cnt,m;
void solve(int u){                               //相当于dfs状态转移之前或回溯继续下一个状态转移的部分 
	while(node[u]<10){                           //状态转移 
		int nex=node[u]+u*10;
		++node[u];
		stack[cnt++]=nex;
		u=nex%m;                                 //状态转移
	}
}
int main(){
	int n,cnt2;
	while(~scanf("%d",&n)&&n){
		if(n==1) {printf("0123456789\n");continue;}
		m=1;
		for(int i=1;i<=n-1;++i) m*=10;
		for(int i=0;i<m;++i) node[i]=0;
		cnt=0,cnt2=0;
		solve(0);
		while(cnt){                              //全部进行dfs 
			int cur=stack[--cnt];                //弹出栈上的 
			ans[cnt2++]=cur%10+'0';              //欧拉回路:在状态转移之后回溯之前部分进行记录 
			solve(cur/10);                       //回溯,当前状态的下一次状态转移 
		}
		for(int i=1;i<=n-1;++i) printf("0");
		while(cnt2) printf("%c",ans[--cnt2]);    //逆序输出:满足字典序 
		printf("\n");
	}
}

HDU 1116 Play on Words
题意:判断是否是欧拉路或欧拉回路
思路:使用dfs判断连通

#include<bits/stdc++.h>
using namespace std;
int n,cnt,cnt2;
int flg[30];//度数 
int vis[30];
vector<int>G[30];
char s[1004];
int ishl;
void dfs(int u){
    for(auto i:G[u]) if(!vis[i]) vis[i]=1,cnt2++,dfs(i);
}
void init(){
    memset(flg,0,sizeof(flg));
    memset(vis,0,sizeof(vis));
    for(int i=0;i<26;++i) G[i].clear();
    cnt2=0,cnt=0;
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        init();
        for(int i=1;i<=n;++i){
            scanf("%s",s);
            int x=s[0]-'a',y=s[strlen(s)-1]-'a';
            flg[y]++,flg[x]--;
            if(!vis[x]) cnt++;
			vis[x]=1;
            if(!vis[y]) cnt++;
            vis[y]=1;
            G[x].push_back(y);
        }
        //首先判断欧拉 
        //欧拉路或者欧拉回路都可以 
        int tmp0=0,tmp1=0,tmpm1=0;
        for(int i=0;i<26;++i){
            if(flg[i]==0) tmp0++;
            if(flg[i]==1) tmp1++;
            if(flg[i]==-1) tmpm1++;
        }
        if(tmp0==24&&tmp1==1&&tmpm1==1) ishl=0;
        else if(tmp0==26) ishl=1;
        else {puts("The door cannot be opened.");continue;}
        
		//然后判断是否连通
        if(ishl)
        	for(int i=0;i<26;++i)
        		if(vis[i]){
        			memset(vis,0,sizeof(vis));
        			cnt2=1,vis[i]=1,dfs(i);break;
				}
		else{
			memset(vis,0,sizeof(vis));
	        for(int i=0;i<26;++i)
	            if(flg[i]==-1){vis[i]=1,cnt2=1,dfs(i);break;}
		}
        if(cnt2==cnt) puts("Ordering is possible.");
        else puts("The door cannot be opened.");
    }
}

HDU 5883 The Best Path
题意:N个点M条边,求点权异或和最大的欧拉(回)路的异或和是多少。
这道题用vector存图然后dfs判断连通会MLE。。。
所以乖巧地使用了并查集
无向图中,
欧拉回路中每个点的经过次数为度数//2,起点(同时也是终点)要另外加1
欧拉路中每个点的经过次数为ceil(度数/2),如:3的时候是2

这道题暴力枚举每个点为起点的情况即可

#include<bits/stdc++.h>
using namespace std;
#define fore(i,x,y) for(int i=x;i<=y;++i)
const int N = 100005;
int fa[N],a[N],in[N];
int cnt=0,cnt2=0;

int find(int x){
	return fa[x]=(fa[x]==x)?x:find(fa[x]);
}

void unite(int x,int y){
	x=find(x),y=find(y);
	if(x==y) return;
	fa[x]=y;
}

int check(int n){         //判断是否有回路
	int cnt1=0,ishl=0,cnt2=0;
	fore(i,1,n) if(in[i]&1) cnt1++; //奇点 
	if(cnt1==2) ;
	else if(cnt1==0) ishl=1;
	else ishl=-1;
	fore(i,1,n) if(i==fa[i]) cnt2++;
	if(cnt2>1) ishl=-1;
	return ishl;
}

int main(){
	int tt;
	scanf("%d",&tt);
	while(tt--){
		int n,m,x,y;
		scanf("%d%d",&n,&m);
		fore(i,1,n) scanf("%d",&a[i]),in[i]=0;
		fore(i,1,m) scanf("%d%d",&x,&y),unite(x,y),in[x]++,in[y]++; //入度 
		int ishl = check(n);
		if(ishl==-1) {puts("Impossible");continue;}
		int ans=0;
		if(ishl){
			int tmp=0;
			fore(i,1,n) fore(j,1,in[i]/2) tmp^=a[i];
			fore(i,1,n) ans=max(ans,tmp^a[i]);          //枚举起始点 
		}
		else fore(i,1,n) fore(j,1,(in[i]+1)/2) ans^=a[i];
		printf("%d\n",ans);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值