拓扑排序 学习笔记
拓扑排序适用于DAG(有向无环图)。它可以按照一定的遍历顺序将点重新排列,能减少算法的复杂度。
先要记录点的入度,将入度为0的点入列,然后与它相邻的点in[]--。如果已经为0,入列,以此类推。在遍历的同时将点按照遍历的顺序记录下来即可。
代码:
while(!q.empty()) { int now=q.front();q.pop(); pos[++cnt]=now; for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; in[to]--; if (!in[to]) q.push(to); } }
下面是几道拓扑排序的题。
T1 小叶子的故事之写代码
题目大意:有N个数。给M个关系(x,y),表示x必须在y之前。输出字典序最小的数列。
---------------------------------------------------------------------------------
拓扑排序裸题,只要用小根堆维护点的顺序即可。
#include<bits/stdc++.h> using namespace std; int in[100005],ans[100005],vis[100005]; int n,m,u,to,jishu; priority_queue<int,vector<int>,greater<int> > q; int cmp(int x,int y) { return x>y; } inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } vector<int> v[100005]; int main() { n=read(),m=read(); jishu=0; for (int i=1;i<=m;i++) { u=read(),to=read(); v[u].push_back(to);in[to]++; } for (int i=1;i<=n;i++) if (in[i]==0) q.push(i); while(!q.empty()) { int now=q.top();q.pop(); ans[++jishu]=now; for (int i=0;i<v[now].size();i++) { int to=v[now][i]; in[to]--; if (in[to]==0&&vis[to]==0) q.push(to),vis[to]=1; } } if (jishu==n) for (int i=1;i<=jishu;i++) printf("%d ",ans[i]); else printf("OMG."); return 0; }
T2 神经网络
----------------------------------
拓扑排序裸题,只不过要判定图是否有环,只要在遍历的时候看看是否有最后不为0的in[]即可。
#include<bits/stdc++.h> using namespace std; int n,p,u,v,d,head[500005],cnt,c[500005],U,st[500005],top,indgr[500005]; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){ if (ch=='-') f=-1; ch=getchar(); }while('0'<=ch&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } return x*f; } struct node { int from,to,dis,next; }edge[500005]; void add(int from,int to,int dis) { edge[++cnt].next=head[from]; edge[cnt].from=from; edge[cnt].to=to; edge[cnt].dis=dis; head[from]=cnt; } void topu() { while(top!=0) { int now=st[top--]; if (c[now]<=0){ for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; indgr[to]--; if (indgr[to]==0) st[++top]=to; } continue; } for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to; c[to]+=c[now]*edge[i].dis; indgr[to]--; if (indgr[to]==0) st[++top]=to; } } } int main() { n=read(),p=read(); for (int i=1;i<=n;i++) { c[i]=read(),U=read(); if (c[i]!=0) st[++top]=i; else c[i]-=U; } for (int i=1;i<=p;i++) { u=read(),v=read(),d=read(); add(u,v,d); indgr[v]++; } topu(); bool flag=0; for (int i=1;i<=n;i++) { if (!head[i]&&c[i]>0){ cout<<i<<" "<<c[i]<<endl; flag=1; } } if (flag==0) cout<<"NULL"<<endl; return 0; }
T3 Fox And Names
--------------------------------------------
披着字符串外衣的拓扑排序。注意转换大小写。其实就是神经网络的加强版。
#include<bits/stdc++.h> using namespace std; vector<int> v[30]; int pos[27],n,in[27],sum; string a,b; int main() { scanf("%d",&n); if (n==1){ for (char i='a';i<='z';i++) cout<<i; return 0; } cin>>b; for (int j=1;j<n;j++) { a=b; cin>>b; for (int i=0;i<a.length();i++) { if (i>=b.length()){ cout<<"Impossible"; return 0; } if (a[i]!=b[i]){ v[a[i]-'a'].push_back(b[i]-'a'); in[b[i]-'a']++; break; } } } queue<int> q; for (int i=0;i<26;i++) if (in[i]==0) q.push(i); while(!q.empty()) { int now=q.front();q.pop(); sum++;pos[sum]=now; for (int i=0;i<v[now].size();i++) { in[v[now][i]]--; if (in[v[now][i]]==0) q.push(v[now][i]); } } if (sum!=26){ cout<<"Impossible"; return 0; } for (int i=1;i<=26;i++) cout<<(char)(pos[i]+'a'); return 0; }
T4 不相交路径
这种题属于拓扑排序里比较难的题了,综合性较强,如果没有做题经验会很头疼。
------------------------------------
后记:拓扑排序和DP结合起来也是一类大问题,比如T4。实际上很多DP题目都可以转化成拓扑排序来做,利用题目给的“优先条件”。至于能否转化,要看各位OIer们的功底了。如果有时间,我会专门写一篇关于DP的随笔,包括这类图上DP的题(也许得要到暑假了,现在做题还是太少。