拓扑排序
性质
给一张图,图中的点之间有先后关系,把这些先后关系转化为图中的边。直到入度为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();
}
}
#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();
}
}
#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();
}
}
#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);
}
}