拓扑排序
拓扑排序
拓扑排序:对一个$DAG$进行拓扑排序,是将所有顶点排成一个线性序列,使得图中任意一对顶点$u$和$v$,若边$(u,v)∈E(G)$,则$u$在线性序列中出现在$v$之前。通常,这样的线性序列称为满足拓扑次序的序列,简称拓扑序列.
上面那个是从百度百科抄下来的.
拓扑排序的基本做法:首先统计每个点的入度和出度,把所有入度为$0$的点压进队列,每次取出队头元素进行更新,并将它连出去的边造成的入度删去,如果有新的点入度变为$0$就放进队列,直到所有点都出队为止.
一般来说拓扑序是不唯一的,很多题都喜欢在这个地方做文章.
一个比较简单的题目:
杂务:https://www.luogu.org/problemnew/show/P1113
题意概述:每个点有一个权值,求所有事务被完成的最小时间(可以同时做很多事情).
其实这道题就是问拓扑序中最靠后的那几个点最早什么时候可以被遍历到,虽然有一个最小化的要求,但是好像没有什么用处,因为可以同时做很多事.
1 # include <cstdio> 2 # include <iostream> 3 # include <vector> 4 # define R register int 5 6 using namespace std; 7 8 int n,x; 9 int a[10009],firs[10009],c[10009]; 10 int vis[10009],dp[10009],h; 11 struct edge 12 { 13 int nex,too; 14 }g[2000009]; 15 16 void add(int x,int y) 17 { 18 g[++h].too=y; 19 g[h].nex=firs[x]; 20 firs[x]=h; 21 } 22 23 int dfs(int x) 24 { 25 if(vis[x]) return dp[x]; 26 dp[x]=a[x]; 27 int j,ans=0; 28 for (int i=firs[x];i;i=g[i].nex) 29 { 30 j=g[i].too; 31 ans=max(ans,dfs(j)); 32 } 33 dp[x]+=ans; 34 vis[x]=true; 35 return dp[x]; 36 } 37 38 int main() 39 { 40 scanf("%d",&n); 41 for (R i=1;i<=n;++i) 42 { 43 scanf("%d%d",&a[i],&a[i]); 44 scanf("%d",&x); 45 while (x) 46 { 47 add(i,x); 48 c[x]++; 49 scanf("%d",&x); 50 } 51 } 52 int ans=0; 53 for (R i=1;i<=n;++i) 54 if(c[i]==0) 55 ans=max(ans,dfs(i)); 56 printf("%d",ans); 57 return 0; 58 }
逛公园:https://www.luogu.org/problemnew/show/P3953
题意概述:求从$1$到$n$长度不超过最短路长度加$k$的路径的条数,无限条输出$-1$.$n<=100000,k<=50$
这是一道好题啊,注意多组数据要清空数组.
首先求出最短路(这样就有$30$分了),然后可以发现这道题的$k$非常小,可以放进状态里一起做,设$dp[i][j]$表示到达第$i$个点时长度比最短路长$j$的方案数,最后答案就是$\sum_{i=0}^kdp[n][i]$.但这就带来了一个问题,应该按照什么顺序更新$dp$数组呢?一般的做法是以最短路为第一关键字,拓扑序为第二关键字来做的,但是也可以使用记忆化搜索,等于说倒着还原了拓扑序列.而且这样还有一个巨大的好处,就是可以两行代码判断零环,使用一个$vis$数组表示当前的搜索栈内有没有出现过$i,j$这个状态,如果已经有这个状态了,现在却要拿这个状态来更新它自己,证明这两个状态间可以通过一些边相互到达,也就是说这个点处出现了一个$0$环.
1 # include <cstdio> 2 # include <iostream> 3 # include <queue> 4 # include <cstring> 5 # define R register int 6 # define mp(a,b) make_pair(a,b) 7 8 using namespace std; 9 10 const int maxn=100009; 11 int a,b,c,h=0,n,m,k,p,t,rx,rf,firs[maxn]; 12 long long d[maxn],ans; 13 int f[maxn][51],sta[maxn][51]; 14 bool inff; 15 bool vis[maxn]; 16 typedef pair <int,int> pii; 17 priority_queue <pii,vector<pii>,greater<pii> > q; 18 19 int read() 20 { 21 int x=0; 22 char c=getchar(); 23 while (!isdigit(c)) 24 c=getchar(); 25 while (isdigit(c)) 26 x=(x<<3)+(x<<1)+(c^48),c=getchar(); 27 return x; 28 } 29 30 struct edge 31 { 32 int frm,too,nex,co; 33 }g[200009]; 34 35 void add(int x,int y,int co) 36 { 37 g[++h].too=y; 38 g[h].co=co; 39 g[h].frm=x; 40 g[h].nex=firs[x]; 41 firs[x]=h; 42 } 43 44 void dfs (int x,int dis) 45 { 46 if(inff) return; 47 int j,k; 48 f[x][dis]=max(f[x][dis],0); 49 if(x==1&&dis==0) f[x][dis]=1; 50 sta[x][dis]=1; 51 for (int i=firs[x];i;i=g[i].nex) 52 { 53 j=g[i].too; 54 k=d[j]+g[i].co-d[x]; 55 if(dis-k<0) continue; 56 if(f[j][dis-k]==-1) dfs(j,dis-k); 57 if(sta[j][dis-k]) inff=true; 58 f[x][dis]+=f[j][dis-k]; 59 if(f[x][dis]>p) f[x][dis]-=p; 60 } 61 sta[x][dis]=0; 62 } 63 64 int main() 65 { 66 t=read(); 67 while (t--) 68 { 69 memset(g,0,sizeof(g)); 70 memset(firs,0,sizeof(firs)); 71 memset(d,1,sizeof(d)); 72 memset(f,-1,sizeof(f)); 73 memset(vis,0,sizeof(vis)); 74 memset(sta,0,sizeof(sta)); 75 while (q.size()) q.pop(); 76 inff=false; 77 h=0; 78 n=read(),m=read(),k=read(),p=read(); 79 for(R i=1;i<=m;i++) 80 { 81 a=read(),b=read(),c=read(); 82 add(a,b,c); 83 } 84 d[1]=0; 85 int beg,j; 86 q.push(mp(0,1)); 87 while (q.size()) 88 { 89 beg=q.top().second; 90 q.pop(); 91 if(vis[beg]) continue; 92 vis[beg]=true; 93 for (R i=firs[beg];i;i=g[i].nex) 94 { 95 j=g[i].too; 96 if(vis[j]) continue; 97 if(d[beg]+g[i].co>=d[j]) continue; 98 d[j]=d[beg]+g[i].co; 99 q.push(mp(d[j],j)); 100 } 101 } 102 int th=h; 103 h=0; 104 ans=0; 105 memset(firs,0,sizeof(firs)); 106 for (R i=1;i<=th;++i) 107 add(g[i].too,g[i].frm,g[i].co); 108 for (R i=0;i<=k;++i) 109 dfs(n,i),ans=(ans+f[n][i])%p; 110 if(inff) printf("-1\n"); 111 else printf("%lld\n",ans); 112 } 113 return 0; 114 }
缩点:https://www.luogu.org/problemnew/show/P3387
题意概述:每个点上有权值,每次经过时可以取走,取走之后就没有了,求一条从$1$到$n$的路径,使得取到的值最大.
直接拓扑排序是错误的,因为有可能根本排不出来,注意每个权值只能算一次且一个强连通分量内的点总是可以互相到达的,所以可以将一个强连通分量视为一个点,权值就是其中每个点的权值和.进行重连边时只连端点分居两个分量内的边,这样新图一定是一个$DAG$.为什么?如果两个连通分量重连边后依然可以相互到达,那他们在之前缩点时就应该已经被缩到一起了.在新图上拓扑排序$dp$就好了.
1 # include <cstdio> 2 # include <iostream> 3 # define R register int 4 5 using namespace std; 6 7 int H,k,bp,cnt,h,Top=0,n,m,x,y,a[10009],firs[10009],Firs[10009],sta[10009]; 8 int id[10009],low[10009],vis[10009],A[10009]; 9 int dp[10009],color[10009],r[10009],q[100009]; 10 struct edge 11 { 12 int too,nex; 13 }g[100009],G[100009]; 14 15 void add1(int x,int y) 16 { 17 g[++h].too=y; 18 g[h].nex=firs[x]; 19 firs[x]=h; 20 } 21 22 void add2(int x,int y) 23 { 24 G[++H].too=y; 25 G[H].nex=Firs[x]; 26 Firs[x]=H; 27 } 28 29 void dfs(int x) 30 { 31 low[x]=id[x]=++cnt; 32 vis[x]=1; 33 sta[++Top]=x; 34 int j; 35 for (R i=firs[x];i;i=g[i].nex) 36 { 37 j=g[i].too; 38 if(!id[j]) 39 { 40 dfs(j); 41 low[x]=min(low[x],low[j]); 42 } 43 else 44 { 45 if(vis[j]) low[x]=min(low[x],low[j]); 46 } 47 } 48 if(id[x]==low[x]) 49 { 50 bp++; 51 color[x]=bp; 52 A[bp]+=a[x]; 53 vis[x]=0; 54 while (sta[Top]!=x) 55 { 56 color[ sta[Top] ]=bp; 57 A[bp]+=a[ sta[Top] ]; 58 vis[ sta[Top] ]=0; 59 Top--; 60 } 61 Top--; 62 } 63 } 64 65 int main() 66 { 67 scanf("%d%d",&n,&m); 68 for (R i=1;i<=n;++i) 69 scanf("%d",&a[i]); 70 for (R i=1;i<=m;++i) 71 { 72 scanf("%d%d",&x,&y); 73 add1(x,y); 74 } 75 for (R i=1;i<=n;++i) 76 if(!id[i]) dfs(i); 77 for (R i=1;i<=n;++i) 78 for (R j=firs[i];j;j=g[j].nex) 79 { 80 k=g[j].too; 81 if(color[i]!=color[k]) add2(color[i],color[k]),r[ color[k] ]++; 82 } 83 int num=0; 84 for (R i=1;i<=bp;++i) 85 if(!r[i]) q[++num]=i,dp[i]=A[i]; 86 for (R i=1;i<=num;++i) 87 { 88 for (R j=Firs[ q[i] ];j;j=G[j].nex) 89 { 90 k=G[j].too; 91 r[k]--; 92 dp[k]=max(dp[k],dp[ q[i] ]+A[k]); 93 if(!r[k]) q[++num]=k; 94 } 95 } 96 int ans=dp[1]; 97 for (R i=2;i<=bp;i++) 98 ans=max(ans,dp[i]); 99 cout<<ans; 100 return 0; 101 }