图论
dijkstra:
///朴素dijkstra算法 —— 模板题 AcWing 849. Dijkstra求最短路 I ///时间复杂是 O(n2+m)O(n2+m), nn 表示点数,mm 表示边数 #include<bits/stdc++.h> using namespace std; const int N=510; int n,m; int g[N][N]; // 存储每条边 int dist[N]; // 存储1号点到每个点的最短距离 bool st[N]; // 存储每个点的最短路是否已经确定 // 求1号点到n号点的最短路,如果不存在则返回-1 int dijkstra() { memset(dist, 0x3f, sizeof dist);//将每个点每个距离赋值为无穷大 dist[1] = 0; for (int i = 0; i < n - 1; i ++ ) { int t = -1; // 在还未确定最短路的点中,寻找距离最小的点 for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; // 用t更新其他点的距离 for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], dist[t] + g[t][j]); st[t] = true; } if (dist[n] == 0x3f3f3f3f) return -1; return dist[n]; } int main() { memset(g,0x3f,sizeof g); cin>>n>>m; while(m--) { int a,b,c; cin>>a>>b>>c; g[a][b]=min(g[a][b],c); } int t=dijkstra(); cout<<t<<endl; }
堆优化版dijkstra:
///堆优化版dijkstra —— 模板题 AcWing 850. Dijkstra求最短路 II ///时间复杂度 O(mlogn)O(mlogn), nn 表示点数,mm 表示边数 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10; int n,m,res,dist[N],s; int e[N],ne[N],h[N],w[N],idx; typedef pair<int,int>pii; void add(int a,int b,int c){ e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++; } bool vis[N]; void dijkstra() { memset(dist,0x3f3f3f,sizeof dist); dist[s]=0; priority_queue<pii,vector<pii>,greater<pii>>que; que.push({0,s});//这里的必须是{0,s},小根堆是对第一个的值进行排序,我们要根据距离排序 while(!que.empty()){ auto now=que.top(); que.pop(); int dis=now.first,head=now.second; if(vis[head]) continue; vis[head]=true;//不加的话时间复杂度会变高 for(int i=h[head];i!=-1;i=ne[i]){ int j=e[i]; if(dist[j]>dis+w[i]){ dist[j]=dis+w[i]; que.push({dist[j],j}); } } } } signed main() { memset(h,-1,sizeof h); cin>>n>>m>>s; for(int i=0;i<m;i++){ int a,b,c; cin>>a>>b>>c; add(a,b,c); } dijkstra(); for(int i=1;i<=n;i++) cout<<dist[i]<<" "; return 0; }
关于所有点到一个点的单源最短路(dijkstra):
其实反着建图就可以了
//https://www.luogu.com.cn/problem/P1342 //https://www.luogu.com.cn/problem/P1629 #include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,mod=1e9+7; int n,m,e[N],ne[N],h[N],w[N],idx,dist[N],res=0; int e1[N],ne1[N],h1[N],w1[N],idx1; typedef pair<int,int>pii; bool vis[N]; void add1(int a,int b, int c){ e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } void add2(int a,int b,int c){ e1[idx1]=b,w1[idx1]=c,ne1[idx1]=h1[a],h1[a]=idx1++; } void dijkstra1(int st){ priority_queue<pii,vector<pii>,greater<pii>>que; for(int i=1;i<=n;i++) dist[i]=0x7f7f7f7f,vis[i]=false; dist[st]=0,que.push({dist[st],st}); while(!que.empty()){ auto now=que.top();que.pop(); int y=now.first,x=now.second; if(vis[x]) continue; vis[x]=true; for(int i=h[x];~i;i=ne[i]){ int j=e[i]; if(dist[j]>y+w[i]){ dist[j]=y+w[i]; que.push({dist[j],j}); } } } } void dijkstra2(int st){ priority_queue<pii,vector<pii>,greater<pii>>que; for(int i=1;i<=n;i++) dist[i]=0x7f7f7f7f,vis[i]=false; dist[st]=0,que.push({dist[st],st}); while(!que.empty()){ auto now=que.top();que.pop(); int y=now.first,x=now.second; if(vis[x]) continue; vis[x]=true; for(int i=h1[x];~i;i=ne1[i]){ int j=e1[i]; if(dist[j]>y+w1[i]){ dist[j]=y+w1[i]; que.push({dist[j],j}); } } } } signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>n>>m; memset(h, -1, sizeof h); memset(h1, -1, sizeof h1); for(int i=1;i<=m;i++){ int a,b,c; cin>>a>>b>>c; add1(a,b,c),add2(b,a,c); } dijkstra1(1); for(int i=2;i<=n;i++) res+=dist[i]; dijkstra2(1); for(int i=2;i<=n;i++) res+=dist[i]; cout<<res<<endl; return 0; }
单源次短路:
//https://www.luogu.com.cn/problem/P2149 #include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,mod=1e9+7; vector<pair<int,int>>point(N); vector<pair<int,double>>g[N]; vector<int>pre(N); vector<double>dijkstra(int n,int aa,int bb){ vector<double>dist(n+1,1e9); vector<bool>vis(n+1,false); priority_queue<pair<double,int>,vector<pair<double,int>>,greater<pair<double,int>>>que; que.push({0,1}),dist[1]=0; while(!que.empty()){ auto now=que.top(); que.pop(); int x=now.second; double y=now.first; if(vis[x]) continue; vis[x]=true; for(auto u:g[x]){ int v=u.first; double w=u.second; if((x==aa&&v==bb)||(x==bb&&v==aa)) continue; if(dist[v]>y+w){ dist[v]=y+w,que.push({dist[v],v}); if(aa==-1) pre[v]=x; } } } return dist; } signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); int n,m,a,b; cin>>n>>m; cin>>a>>b; point[1]={a,b}; for(int i=2;i<=n-1;i++){ cin>>a>>b; point[i]={a,b}; } vector<vector<double>>dis(n+1,vector<double>(n+1)); cin>>a>>b,point[n]={a,b}; auto get_distance=[](pair<int,int> a,pair<int,int> b){ int x=a.first,y=a.second,xx=b.first,yy=b.second; return (double)(sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy))); }; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=get_distance(point[i],point[j]); for(int i=1;i<=m;i++){ cin>>a>>b; double c=dis[a][b]; g[a].push_back({b,c}),g[b].push_back({a,c}); } vector<double>ans=dijkstra(n,-1,-1); // for(int i=1;i<=n;i++) cout<<ans[i]<<' '; cout<<'\n'; double res=1e9; for(int i=n;i!=1;i=pre[i]){ int aa=i,bb=pre[i]; ans=dijkstra(n,aa,bb); // for(int i=1;i<=n;i++) cout<<ans[i]<<' '; cout<<'\n'; res=min(res,ans[n]); } if(res==1e9) cout<<"-1"<<'\n'; else printf("%.2lf\n",res); return 0; }
最短路计数,使用邻接表动态数组,bfs:
///最短路计数,使用邻接表动态数组,bfs #include<bits/stdc++.h> using namespace std; const int N=1000010,mod=100003; vector<int> g[N]; int dist[N]; bool vis[N]; int cnt[N]; int n,m; void bfs() { memset(dist,0x3f3f3f,sizeof dist); memset(vis,false,sizeof vis); queue<int>que; dist[1]=0; cnt[1]=1; que.push(1); while(!que.empty()) { int u=que.front(); que.pop(); if(vis[u]) continue; vis[u]=true; for(auto v:g[u]) { if(!vis[v]) { if(dist[v]>dist[u]+1)///如果此时不是1最短路,更新最短路 { dist[v]=dist[u]+1; cnt[v]=cnt[u]; cnt[v]%=mod; que.push(v); } else if(dist[v]==dist[u]+1)///如果走的路是最短路,就在这个地方加上1; { cnt[v]+=cnt[u]; cnt[v]%=mod; } } } } } int main() { cin>>n>>m; for(int i=0;i<m;i++) { int a,b; cin>>a>>b; g[a].push_back(b);///储存邻接表 g[b].push_back(a); } bfs(); for(int i=1;i<=n;i++) cout<<cnt[i]<<endl; return 0; }
ballman-ford:
#include<bits/stdc++.h> using namespace std; const int N=10010; int n,m,k; int dist[N],backup[N]; struct node { int a,b,w; }edge[N]; int ballman_ford() { memset(dist,0x3f,sizeof dist); dist[1]=0; for(int i=0;i<k;i++) { memcpy(backup,dist,sizeof dist); for(int j=0;j<m;j++) { int a=edge[j].a,b=edge[j].b,w=edge[j].w; dist[b]=min(dist[b],backup[a]+w); } } if(dist[n]>0x3f3f/2) return -1; else return dist[n]; } int main() { cin>>n>>m>>k; for(int i=0;i<m;i++) { int a,b,w; cin>>a>>b>>w; edge[i]={a,b,w}; } int t =ballman_ford(); if(t==-1) puts("impossible"); else cout<<t<<endl; return 0; }
SPFA:
#include <bits/stdc++.h> using namespace std; const int N = 2030, M = N * 50; //N为顶点数,M为边数 //数组构造邻接表, n为定点数,m为边数 int n, m int h[N], e[M], w[M], ne[M], idx; int dist[N]; queue<int> q; bool st[N]; // 添加一条边a->b,边权为c void add(int a, int b, int c) { //e[idx]指的是编号为idx的边的去向为何处,h[a]存储的是从a出发的所有边的单链表表头 e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; } void spfa() // 求1号点到n号点的最短路距离 { memset(dist, 0x3f, sizeof dist); //0x3f代表最大值,最小值可用0xcf dist[1] = 0; q.push(1); st[1] = true; while (q.size()) //while循环控制出队 { int t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; i != -1; i = ne[i]) //for循环控制入队+更新 { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; // cnt[j] = cnt[t] + 1; // 用于判断是否存在负环 // pre[i] = t; // 用于记录前驱节点(记录路径) if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入 { q.push(j); st[j] = true; } // if(cnt[j] >= n) return true; // 用于判断是否存在负环 } // 最短路计数要区分dist[j] > 和 == 的情况,不能只讨论dist[j] > dist[t] + w[i] /** if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] } else if (dist[j] == dist[t] + w[i]) { cnt[i] += cnt[t]; //最短路计数 } if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入 { q.push(j); st[j] = true; } */ } } } int main(){ memset(h, -1, sizeof h); ... //需先通过add函数,采取邻接表的方式构造好图 spfa(); ... //输出路径权值和、判断是否有负环、输出最短路径...等操作 /** 记录路径操作 */ stack<int> help; //遍历前驱节点,压入栈中,再取出来时可以变为正序的 for (int i = n; ~i; i=pre[i]) help.push(i); // n为路径终点 while (help.size()) { printf("%d ", help.top()); help.pop(); } }
///SPFA路径 ///使用的邻接表储存 #include<bits/stdc++.h> using namespace std; const int N=10010; int n,m; int h[N],e[N],ne[N],w[N],idx; int dist[N]; bool st[N]; void add(int a,int b,int c) { e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } int spfa() { memset(dist,0x3f,sizeof dist); dist[1]=0; queue<int>que; que.push(1); st[1]=true; while(!que.empty()) { int now=que.front(); que.pop(); st[now]=false; for(int i=h[now];i!=-1;i=ne[i]) { int j=e[i]; if(dist[j]>dist[now]+w[i]) { dist[j]=dist[now]+w[i]; if(!st[j]) { que.push(j); st[j]=true; } } } } if(dist[n]>0x3f3f/2) return -1; else return dist[n]; } int main() { cin>>n>>m; memset(h,-1,sizeof h); while(m--) { int a,b,c; cin>>a>>b>>c; add(a,b,c); } int t=spfa(); if(t==-1) cout<<"impossible"<<endl; else cout<<t<<endl; return 0; }
///二维数组存SPFA #include<bits/stdc++.h> using namespace std; const int N=10010; struct node { int b,w; }e; int n,m; int dist[N]; bool st[N]; vector<node>g[N]; int spfa() { memset(dist,0x3f3f,sizeof dist); dist[1]=0; queue<int>que; que.push(1); st[1]=true;///表示我们的这个点已经入队列了 while(!que.empty()) { int now=que.front(); que.pop(); st[now]=false;///队头出去了,那就是这个点不在这个队列里了标记false; for(int i=0;i<g[now].size();i++) { int v=g[now][i].b; if(dist[v]>dist[now]+g[now][i].w) { dist[v]=dist[now]+g[now][i].w; if(!st[v])///如果队列里面没有他,再加入队列; { st[v]=true; que.push(v); } } } } if(dist[n]>0x3f3f/2) return -1; else return dist[n]; } int main() { cin>>n>>m; for(int i=0;i<m;i++) { int a; cin>>a>>e.b>>e.w; g[a].push_back(e);///存入数据 } int t=spfa(); if(t==-1) cout<<"impossible"<<endl; else cout<<t<<endl; return 0; }
SPFA判断负环:
#include<bits/stdc++.h> #define int long long using namespace std; const int N=2e5; int cnt[N*2],res,n,m,dist[N*2],t; struct node { int b,w; }e; bool vis[2*N]; vector<node>g[N*2]; bool spfa() { queue<int>que; dist[1]=0,vis[1]=true; que.push(1); while(!que.empty()){ int now=que.front();que.pop(); vis[now]=false;//队头出去了,那就标记不在 for(int i=0;i<g[now].size();i++){ int v=g[now][i].b; if(dist[v]>dist[now]+g[now][i].w){ dist[v]=dist[now]+g[now][i].w; cnt[v]=cnt[now]+1;//路的边数+1 if(cnt[v]>=n) return false;//如果,我是说如果当前路的边数已经大于n了,那很明显存在负环; if(!vis[v]) vis[v]=true,que.push(v); } } } return true; } signed main() { cin>>t; while(t--){ memset(cnt,0,sizeof cnt); memset(vis,false,sizeof vis); memset(dist,0x3f,sizeof dist); for (int j=0;j<=N;j++) g[j].clear(); cin>>n>>m; bool f=false; for(int i=0;i<m;i++){ int a,b,c; cin>>a>>b>>c; if(a==1) f=true; if(c>=0){ g[a].push_back({b,c}); g[b].push_back({a,c}); } else g[a].push_back({b,c}); } if(!spfa()) cout<<"YES"<<endl; else cout<<"NO"<<endl; } return 0; }
floyd算法,适用于多个询问:
#include<bits/stdc++.h> using namespace std; const int N=1010; int n,m,k; int d[N][N]; void floyd() { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int x=1;x<=n;x++) d[j][x]=min(d[j][x],d[j][i]+d[i][x]); } int main() { cin>>n>>m>>k; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i==j) d[i][j]=0; else d[i][j]=1e9; while(m--) { int a,b,w; cin>>a>>b>>w; d[a][b]=min(d[a][b],w); } floyd(); while(k--) { int a,b; cin>>a>>b; if(d[a][b]>1e9/2) cout<<"impossible"<<endl; else cout<<d[a][b]<<endl; } return 0; }
Floyd应用:
///floyd算法的应用 //https://www.luogu.com.cn/record/110497608 ///floyd算法应用与求每个点到任意一点的最短距离,然后根据此算法就可以按照题目要求的路径算出最短路径来 ///不需要建图,三重暴力循环解决战斗 #include<bits/stdc++.h> using namespace std; const int N=1010; int n,m,num[100005],d[N][N],res; int main() { cin>>n>>m; for(int i=1;i<=m;i++) { cin>>num[i]; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>d[i][j]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) d[j][k]=min(d[j][k],d[j][i]+d[i][k]); int st=1,ed=n; for(int i=1;i<=m;i++) { res+=d[st][num[i]]; st=num[i]; } res+=d[st][ed]; cout<<res<<endl; return 0; } ///floyd 应用2 变形 //https://www.luogu.com.cn/problem/B3611 #include<bits/stdc++.h> using namespace std; const int N=500; int n,d[N][N],mp[N][N]; int main() { cin>>n;; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>d[i][j]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) d[j][k]=max(d[j][k],min(d[j][i],d[i][k]));///1表示为连通,0表示为不连通,试想一下,如果d[j][k]和另一个有一个为1,那是不是就是可以连通? ///但是这里为什么第二个要取min呢,如果这两者有一个为0,说明,j和k之间没有中转站,此时如果j和k不是直接连的也就是,d[j][k]不是1,那么这两点不可能连上; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) cout<<d[i][j]<<" "; cout<<endl; } return 0; }
// [USACO08JAN] Cow Contest S // floyd不仅能求出最短距离,还能代表当前点i可以走到点j #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e3; int n,m,res,g[N][N],num[N]; void floyd() { for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) g[i][j]=min(g[i][j],g[i][k]+g[k][j]); } signed main() { cin>>n>>m; for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) g[i][j]=2e9; for(int i=0;i<m;i++){ int a,b; cin>>a>>b; g[a][b]=1; } floyd(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(g[i][j]<2e9/2&&i!=j) num[i]++,num[j]++; for(int i=1;i<=n;i++) if(num[i]>=n-1) res++; cout<<res; return 0; }
//https://www.luogu.com.cn/problem/P1119 //从本质理解folyd算法,进行实时的更新,时间复杂度O(n*m*n+q) #include<bits/stdc++.h> using namespace std; const int N=2e5+10,M=500; int n,m,f[M][M],t[N],q,now; int main(){ cin>>n>>m; memset(f,0x3f,sizeof f); for(int i=0;i<n;i++) cin>>t[i],f[i][i]=0; while(m--){ int a,b,c; cin>>a>>b>>c; f[a][b]=f[b][a]=c; } cin>>q; while(q--){ int a,b,c; cin>>a>>b>>c; while(t[now]<=c&&now<n){ for(int i=0;i<n;i++) for(int j=0;j<n;j++) f[i][j]=min(f[i][j],f[i][now]+f[now][j]); now++; } if(t[a]>c||t[b]>c||f[a][b]==0x3f3f3f3f) cout<<-1<<endl; else cout<<f[a][b]<<endl; } }
分层图:
///分层图 //https://blog.csdn.net/qq_45735851/article/details/108219481?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168420864216800222869973%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=168420864216800222869973&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-108219481-null-null.142^v87^control,239^v2^insert_chatgpt&utm_term=%E5%88%86%E5%B1%82%E5%9B%BE%E6%9C%80%E7%9F%AD%E8%B7%AF&spm=1018.2226.3001.4187 ///分层图最经典的例题之一 ///可以试想一下,咱们最短路是一个图,分层图的意思是创建多个图,然后每个图都不一样,遍历完之后找到最优解 ///可以看出来分层图难在建图 #include<bits/stdc++.h> using namespace std; const int N=10000010; int n,m,s,k,t,idx; int e[N],ne[N],h[N],dist[N],w[N]; bool vis[N]; typedef pair<int,int>pii; void add(int a,int b,int c) { e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++; } void dijkstra()///标准模板 { priority_queue<pii,vector<pii>,greater<pii>> que; memset(dist,0x3f3f3f,sizeof dist); dist[s]=0; que.push({0,s}); while(!que.empty()) { auto now=que.top(); que.pop(); int y=now.first,x=now.second; for(int i=h[x];i!=-1;i=ne[i]) { int j=e[i]; if(dist[j]>y+w[i]) { dist[j]=y+w[i]; que.push({dist[j],j}); } } } } int main() { memset(h,-1,sizeof h); cin>>n>>m>>k>>s>>t; for(int i=0;i<m;i++) { int a,b,c; cin>>a>>b>>c; add(a,b,c); //add(b,a,c); for(int j=1;j<=k;j++)///建图 { add(j*n+a,j*n+b,c);///把一层建好 //add(j*n+b,j*n+a,c); add((j-1)*n+a,j*n+b,0);///把上一层建好,代表从上到下走0的距离; // add((j-1)*n+b,j*n+a,0); } } for(int i=0;i<k;i++) add(i*n+t,(i+1)*n+t,0);///有可能答案不需要非要用完k次免费次数,所以这里把所有终点都用距离为0连起来; dijkstra(); int minn=0x3f; for(int i=0;i<=k;i++) minn=min(minn,dist[i*n+t]);///遍历所有终点,有可能没必要用完k次; cout<<minn; return 0; }
差分约束:
//差分约束算法 // 差分约束 // (1)求不等式组的可行解 // 源点需要满足的条件: 从源点出发,一定可以走到所有的边。步骤: // [1] 先将每个不等式 xi <= xj + ck, 转化成一条从xj走到xi,长度为ck的一条边 // [2] 找一个超级源点,使得该源点一定可以遍历到所有边 // [3] 从源点求一遍单源最短路 // 结果1: 如果存在负环,则原不等式组一定无解 // 结果2: 如果没有负环,则dist[i]就是原不等式组的一个可行解 // (2) 如何求最大值或者最小值,这里的最值指的是每个变量的最值 // 结论: 如果求的是最小值,则应该求最长路;如果求的是最大值,则应该求最短路 // 问题: 如何转化xi < = c,其中c是一个常数,这类的不等式 // 方法: 建立一个超级源点,0,然后建立0->i,长度是c的边即可。以求xi的最大值为例: // 求所有从xi出发,构成的不等式链xi <= xj + c1 <= xk + c2 + c1 <= ... <= x0+c1+c2+...所计算出的上界 // 最终xi的最大值等于所有上界的最小值。 //【模板】差分约束算法 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+10; int n,m,dist[N],cnt[N],s,res; int e[N],ne[N],h[N],w[N],idx; bool vis[N]; void add(int a,int b,int c) { e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++; } bool spfa() { queue<int>que; que.push(0),dist[0]=0,vis[0]=true; while(!que.empty()){ int now=que.front(); que.pop(); vis[now]=false,s++; for(int i=h[now];i!=-1;i=ne[i]){ int j=e[i]; if(dist[j]>dist[now]+w[i]){ dist[j]=dist[now]+w[i]; cnt[j]=cnt[now]+1; if(cnt[now]>=n+1||s>1e6) return false; if(!vis[j]) vis[j]=true,que.push(j); } } } return true; } signed main() { memset(h,-1,sizeof h); memset(dist,0x3f,sizeof dist); cin>>n>>m; for(int i=0;i<m;i++){ int a,b,c; cin>>a>>b>>c; add(b,a,c);//切记,是从b向a建立一条边 //因为我们是从查询的点出发,然后一直查询到0,所以是从j->i的边 } for(int i=n;i;i--) add(0,i,1); if(!spfa()) cout<<"NO"<<endl; else for(int i=1;i<=n;i++) cout<<dist[i]<<" "; return 0;
差分约束应用:
//糖果 //https://www.luogu.com.cn/problem/P3275 //队列版 #include<bits/stdc++.h> #define int long long using namespace std; const int N=5e5; int n,m,res,dist[N],cnt[N],s; int e[N],ne[N],w[N],h[N],idx; bool vis[N]; void add(int a,int b,int c) { e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++; } bool spfa() { memset(dist,-0x3f,sizeof dist); queue<int>que; que.push(0),dist[0]=0,vis[0]=true;//从0开始 while(!que.empty()){ int now=que.front(); que.pop(); vis[now]=false,s++; for(int i=h[now];i!=-1;i=ne[i]){ int j=e[i]; if(dist[j]<dist[now]+w[i]){ dist[j]=dist[now]+w[i]; cnt[j]=cnt[now]+1; if(cnt[j]>=n+1||s>1e6) return false;//因为从0开始,所以是n+1次 if(!vis[j]) vis[j]=true,que.push(j); } } } return true; } signed main() { memset(h,-1,sizeof h); cin>>n>>m; for(int i=0;i<m;i++){ int a,b,c; cin>>c>>a>>b; if(c==1) add(b,a,0),add(a,b,0); else if(c==2) add(a,b,1); else if(c==3) add(b,a,0); else if(c==4) add(b,a,1); else add(a,b,0); } for(int i=n;i;i--) add(0,i,1);//将所有的点从0点连通 if(!spfa()) cout<<-1<<endl; else{ for(int i=1;i<=n;i++) res+=dist[i]; cout<<res<<endl; } return 0; } //栈版(有时候会比队列快) #include<bits/stdc++.h> #define int long long using namespace std; const int N=5e5; int n,m,res,dist[N],cnt[N],s; int e[N],ne[N],w[N],h[N],idx; bool vis[N]; void add(int a,int b,int c) { e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++; } bool spfa() { memset(dist,-0x3f,sizeof dist); stack<int>que; que.push(0),dist[0]=0,vis[0]=true;//从0开始 while(!que.empty()){ int now=que.top(); que.pop(),vis[now]=false,s++; for(int i=h[now];i!=-1;i=ne[i]){ int j=e[i]; if(dist[j]<dist[now]+w[i]){ dist[j]=dist[now]+w[i]; cnt[j]=cnt[now]+1; if(cnt[j]>=n+1||s>1e6) return false;//因为从0开始,所以是n+1次 if(!vis[j]) vis[j]=true,que.push(j); } } } return true; } signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); memset(h,-1,sizeof h); cin>>n>>m; for(int i=0;i<m;i++){ int a,b,c; cin>>c>>a>>b; if(c==1) add(b,a,0),add(a,b,0); else if(c==2) add(a,b,1); else if(c==3) add(b,a,0); else if(c==4) add(b,a,1); else add(a,b,0); } for(int i=n;i;i--) add(0,i,1); if(!spfa()) cout<<-1<<endl; else{ for(int i=1;i<=n;i++) res+=dist[i]; cout<<res<<endl; } return 0; }
最小生成树:
//最小生成树 //朴素prim: //把所有距离初始化为正无穷,然后每循环一次寻找距离集合最近的距离,并更新 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e3+10; int n,m,dist[N],g[N][N],res; bool vis[N]; int prim() { dist[1]=0; for(int i=0;i<n;i++){ int t=-1; for(int j=1;j<=n;j++)//寻找最短距离的点 if(!vis[j]&&(t==-1||dist[t]>dist[j])) t=j; if(i){ if(dist[t]=0x3f3f3f3f) return 0x3f3f3f3f; res+=dist[t]; } for(int j=1;j<=n;j++) dist[j]=min(dist[j],g[t][j]);//更新最短距离 vis[t]=true; } return res; } signed main() { cin>>n>>m; memset(dist,0x3f,sizeof dist); memset(g,0x3f,sizeof g); for(int i=0;i<m;i++){ int a,b,c; cin>>a>>b>>c; g[a][b]=g[b][a]=min(g[a][b],c); } res=prim(); if(res>=0x3f3f3f3f/2) cout<<"orz"<<endl; else cout<<res<<endl; return 0; } //kruskal(速度快) //将所有的边按权重进行快速排序 //枚举每条边,如果a,b不连通,就加到集合去 #include<bits/stdc++.h> #define int long long using namespace std; const int N=5010; int p[N],n,m,res,cnt; struct node { int a,b,w; bool operator<(const node&W)const{ return w<W.w; } }edge[N]; int find(int x) { if(x!=p[x]) p[x]=find(p[x]); return p[x]; } signed main() { cin>>n>>m; for(int i=0;i<m;i++){ int a,b,w; cin>>a>>b>>w; edge[i]={a,b,w}; } sort(edge,edge+m); for(int i=0;i<m;i++){ int a=edge[i].a,b=edge[i].b,w=edge[i].w; if(find(a)!=find(b)) p[find(a)]=b,res+=w,cnt++; } if(cnt<n-1) cout<<"orz"<<endl; else cout<<res<<endl; return 0; }
最小生成树的应用:
//https://www.luogu.com.cn/problem/P1194 //买礼物 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10; int n,m,res,p[N],cnt; struct node { int a,b,w; bool operator<(const node&W)const { return w<W.w; } }edge[N]; int find(int x){ if(x!=p[x]) p[x]=find(p[x]); return p[x]; } signed main() { cin>>m>>n; for(int i=0;i<=n;i++){ //看懂题意,开局先存边 edge[cnt++]={0,i,m}; } for(int i=0;i<n;i++) for(int j=0;j<n;j++){ int a; cin>>a; if(a) edge[cnt++]={i,j,a}; } sort(edge,edge+cnt); for(int i=0;i<=cnt;i++) p[i]=i; for(int i=0;i<cnt;i++){ int a=edge[i].a,b=edge[i].b; if(find(a)!=find(b)) res+=edge[i].w,p[find(a)]=find(b); } cout<<res<<endl; return 0; } //https://www.luogu.com.cn/problem/P1195 //口袋的天空 //只需要将代价前n-k小的合成一块就行,比如有10个,需要合成7个 //只需要让6个是独自一个,其他四个合成1个就ok #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10; int n,m,res,cnt,p[N],k,num; struct node { int a,b,w; bool operator<(const node&W)const{ return w<W.w; } }q[N]; int find(int x){ if(x!=p[x]) p[x]=find(p[x]); return p[x]; } signed main() { cin>>n>>m>>k; for(int i=0;i<m;i++){ int a,b,c; cin>>a>>b>>c; q[i]={a,b,c}; } sort(q,q+m); for(int i=0;i<=n;i++) p[i]=i; for(int i=0;i<m;i++){ int a=q[i].a,b=q[i].b; if(find(a)!=find(b)){ cnt++; p[find(a)]=find(b); res+=q[i].w; if(cnt>=n-k) break; } } if(cnt<k-1) cout<<"No Answer"<<endl; else cout<<res<<endl; return 0; } //https://www.luogu.com.cn/problem/P1396 //营救 //正难则反,速成最大生成树? //考虑把所有的桥都拆了,然后重新修建 //为什么是从大到小排序? //划分到不同连通块,实际上相当于把一个关键点与其他非关键点连通,并且不与其他关键点联通,也就把删边转化为了加边。 //既然是加边,求得是最小的摧毁代价,那么我们就求最大的修建代价,然后用总的减去就可以了,所以要从大到小 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e7+10; int n,m,res,p[N],e; bool vis[N]; struct node { int a,b,w; bool operator<(const node&W) const { return w>W.w; } }q[N]; int find(int x) { if(x!=p[x]) p[x]=find(p[x]); return p[x]; } signed main() { cin>>n>>m; for(int i=0;i<m;i++) cin>>e,vis[e]=true;//标记敌人 for(int i=1;i<n;i++){ int a,b,c; cin>>a>>b>>c; q[i]={a,b,c},res+=c; } sort(q+1,q+n);//从大到小排序,先修代价大的 for(int i=0;i<=n;i++) p[i]=i; for(int i=1;i<n;i++){ int a=find(q[i].a),b=find(q[i].b); if(vis[a]&&vis[b]) continue;//注意,这里不能修两个敌人节点的桥 p[a]=b,res-=q[i].w,vis[a]=vis[b]=vis[a]||vis[b];//如果一个正常节点连接上了敌人节点,那么这个正常节点也会变为敌人 //如果不标记的话,就会可以通过i->j->k,j这个间接节点到达,达不到孤立的效果 } cout<<res; return 0; }
最近公共祖先(LCA):
(1)倍增:
//最近公共祖先 // (1) 向上标记法 O(n) // (2) 倍增 // fa[i, j] 表示从i开始,向上走2^j步所能走到的节点。0 <= j <= logn,depth[i] 表示深度 // 哨兵: 如果从i开始跳2^j步会跳过根节点,那么fa[i,j] = 0。depth[O] = 0 // 步骤: // [1] 先将两个点跳到同一层 // [2] 让两个点同时往上跳,一直跳到它们的最近公共祖先的下一层。 // 预处理 O(nlogn) // 查询 O(logn) //http://ybt.ssoier.cn:8088/problem_show.php?pid=1557 //孙祖关系 #include<bits/stdc++.h> using namespace std; const int N=40010; int n,m,res,depth[N],fa[N][16]; int e[N*2],ne[N*2],h[N],idx,root; void add(int a,int b){ e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void bfs(int root) { memset(depth,0x3f,sizeof depth); depth[0]=0,depth[root]=1; queue<int>que; que.push(root); while(!que.empty()){ int now=que.front(); que.pop(); for(int i=h[now];i!=-1;i=ne[i]){ int j=e[i]; if(depth[j]>depth[now]+1){ depth[j]=depth[now]+1,fa[j][0]=now,que.push(j); for(int k=1;k<=15;k++) fa[j][k]=fa[fa[j][k-1]][k-1]; } } } } int lca(int a,int b) { if(depth[a]<depth[b]) swap(a,b); for(int k=15;k>=0;k--){ if(depth[fa[a][k]]>=depth[b]) a=fa[a][k]; } if(a==b) return a; for(int k=15;k>=0;k--) if(fa[a][k]!=fa[b][k]) a=fa[a][k],b=fa[b][k]; return fa[a][0]; } int main() { scanf("%d",&n); memset(h,-1,sizeof h); for(int i=0;i<n;i++){ int a,b; scanf("%d%d",&a,&b); if(b==-1) root=a; else add(a,b),add(b,a); } bfs(root); cin>>m; while(m--){ int a,b; scanf("%d%d",&a,&b); int p=lca(a,b); if(p==a) puts("1"); else if(p==b) puts("2"); else puts("0"); } return 0; }
//https://www.luogu.com.cn/problem/P3884 #include<bits/stdc++.h> using namespace std; const int N=2e5+10; int e[N],ne[N],idx,h[N],num[N],dist[N]; int fa[N][16],n,m,res,ans,dis,st,ed; int dep,wid; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void bfs() { memset(dist,0x3f,sizeof dist); queue<int>que; que.push(1),dist[1]=1,dist[0]=0; while(!que.empty()){ int now=que.front(); que.pop(); for(int i=h[now];~i;i=ne[i]){ int j=e[i]; if(dist[j]>dist[now]+1){ dist[j]=dist[now]+1; dep=max(dep,dist[j]); fa[j][0]=now,que.push(j); for(int k=1;k<=15;k++) fa[j][k]=fa[fa[j][k-1]][k-1]; } } } } int lca(int a,int b) { if(dist[a]<dist[b]) swap(a,b); for(int k=15;k>=0;k--) if(dist[fa[a][k]]>=dist[b]) a=fa[a][k]; if(a==b) return a; for(int k=15;k>=0;k--) if(fa[a][k]!=fa[b][k]) a=fa[a][k],b=fa[b][k]; return fa[a][0]; } int main() { memset(h,-1,sizeof h); cin>>n; for(int i=1;i<n;i++){ int u,v; cin>>u>>v; add(u,v); } bfs(); for(int i=1;i<=n;i++){ num[dist[i]]++; if(wid<num[dist[i]]) wid=num[dist[i]]; } cin>>st>>ed; cout<<dep<<endl<<wid<<endl<<(dist[st]-dist[lca(st,ed)])*2+dist[ed]-dist[lca(st,ed)]; return 0; }
(2)Tarjan:
// (3) Tarjan一离线求LCA O(n + m) // 在深度优先遍历时,将所有点分成三大类 // [1] 已经遍历过,且回溯过的点 // [2] 正在搜索的分支 // [3] 还未搜索到的点 //http://ybt.ssoier.cn:8088/problem_show.php?pid=1552 //点的距离 #include<bits/stdc++.h> using namespace std; const int N=2e5+10; int n,m,res[N],p[N],dist[N]; int e[N],ne[N],w[N],h[N],idx; typedef pair<int,int>pii; vector<pii>query[N]; int vis[N]; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } int find(int x) { if(x!=p[x]) p[x]=find(p[x]); return p[x]; } void dfs(int u,int fa) { for(int i=h[u];i!=-1;i=ne[i]){ int j=e[i]; if(j==fa) continue; dist[j]=dist[u]+1; dfs(j,u); } } void tarjan(int u) { vis[u]=1; for(int i=h[u];i!=-1;i=ne[i]){ int j=e[i]; if(!vis[j]) tarjan(j),p[j]=u; } for(auto i:query[u]){ int x=i.first,id=i.second; if(vis[x]==2){ int anc=find(x); res[id]=dist[u]+dist[x]-2*dist[anc]; } } vis[u]=2; } signed main() { scanf("%d",&n); memset(h,-1,sizeof h); for(int i=0;i<n-1;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b),add(b,a); } cin>>m; for(int i=0;i<m;i++){ int a,b; scanf("%d%d",&a,&b); if(a!=b) query[a].push_back({b,i}),query[b].push_back({a,i}); } for(int i=0;i<=n;i++) p[i]=i; dfs(1,-1);tarjan(1); for(int i=0;i<m;i++) printf("%d\n",res[i]); return 0; }
(3) 树剖LCA
#include <bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,mod=1e9+7; vector<int>g[N]; int top[N],fa[N],dep[N],son[N],sz[N]; void dfs1(int u){ sz[u]=1,dep[u]=dep[fa[u]]+1; for(auto x:g[u]){ if(x==fa[u]) continue; fa[x]=u; dfs1(x); sz[u]+=sz[x]; if(sz[son[u]]<sz[x]) son[u]=x; } } void dfs2(int u,int h){ top[u]=h; if(son[u]) dfs2(son[u],h); for(auto x:g[u]){ if(x==fa[u]||x==son[u]) continue; dfs2(x,x); } } int lca(int a,int b){ while(top[a]!=top[b]){ if(dep[top[a]]>dep[top[b]]) a=fa[top[a]]; else b=fa[top[b]]; } return dep[b]>dep[a]?a:b; } signed main(){ std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); int n,m,s; cin>>n>>m>>s; for(int i=1;i<n;i++){ int a,b; cin>>a>>b; g[a].push_back(b),g[b].push_back(a); } dfs1(s),dfs2(s,s); while(m--){ int a,b; cin>>a>>b; cout<<lca(a,b)<<endl; } }
拓扑排序:
//拓扑排序 //拓扑排序=有向无环图 //家谱树 //https://www.luogu.com.cn/problem/B3644 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1000; int n,m,res,a[N],p[N],top[N]; int e[N],ne[N],h[N],idx; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void topsort() { queue<int>que; for(int i=1;i<=n;i++) if(p[i]==0) top[res++]=i,que.push(i); while(!que.empty()){ int now=que.front(); que.pop(); for(int i=h[now];~i;i=ne[i]){ int j=e[i]; p[j]--; if(p[j]==0) top[res++]=j,que.push(j); } } } signed main() { cin>>n; memset(h,-1,sizeof h); for(int i=1;i<=n;i++) while(cin>>m&&m!=0) add(i,m),p[m]++; topsort(); for(int i=0;i<res;i++) cout<<top[i]<<" "; return 0; } //奖金 //http://ybt.ssoier.cn:8088/problem_show.php?pid=1352 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10; int n,m,res,num,p[N],top[N]; int e[N],ne[N],h[N],idx; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void topsort() { queue<int>que; for(int i=1;i<=n;i++) if(!p[i]) top[i]=100,que.push(i); while(!que.empty()){ int now=que.front(); que.pop(); for(int i=h[now];~i;i=ne[i]){ int j=e[i]; p[j]--,top[j]=top[now]+1; if(p[j]==0) que.push(j); } } } signed main() { cin>>n>>m; memset(h,-1,sizeof h); for(int i=0;i<m;i++){ int a,b; cin>>a>>b; add(b,a),p[a]++; } topsort(); for(int i=1;i<=n;i++){ if(top[i]==0) return cout<<"Poor Xed",0; res+=top[i]; } cout<<res; return 0; } //(2)差分约束算法 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+10; int n,m,res,top[N],cnt[N],s; int e[N],ne[N],h[N],idx,w[N]; bool vis[N]; void add(int a,int b,int c) { e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++; } bool spfa() { queue<int>que; top[0]=99; que.push(0),vis[0]=true; while(!que.empty()){ int now=que.front(); que.pop(); vis[now]=false; s++; for(int i=h[now];i!=-1;i=ne[i]){ int j=e[i]; if(top[j]<top[now]+1){ top[j]=top[now]+1; cnt[j]=cnt[now]+1; if(cnt[j]>=n+1) return false; if(!vis[j]) vis[j]=true,que.push(j); } } } return true; } signed main() { cin>>n>>m; memset(h,-1,sizeof h); memset(top,-0x3f,sizeof top); for(int i=0;i<m;i++){ int a,b; cin>>a>>b; add(b,a,1); } for(int i=n;i>=1;i--) add(0,i,1); if(!spfa()) cout<<"Poor Xed"; else{ for(int i=1;i<=n;i++) res+=top[i]; cout<<res; } return 0; } //车站分级 //https://www.luogu.com.cn/problem/P1983 //拓扑排序 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10,M=1e4+10; int n,m,res,p[N],top[N],k,s[N]; int e[N],ne[N],h[N],idx,num; bool vis[N],mp[M][M];//mp数组要开小,防止mle void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void topsort() { queue<int>que; for(int i=1;i<=n;i++) if(!p[i]) que.push(i); while(!que.empty()){ int now=que.front(); que.pop(); for(int i=h[now];~i;i=ne[i]){ int j=e[i]; p[j]--; if(p[j]==0){ top[j]=top[now]+1;//如果没有比他小的了,他就是最小的,此时级别+1 que.push(j); } } } } signed main() { cin>>n>>m; memset(h,-1,sizeof h); for(int i=0;i<m;i++){ //建图 cin>>k; int st,ed; memset(vis,false,sizeof vis); for(int j=0;j<k;j++){ cin>>s[j]; vis[s[j]]=true; if(j==0) st=s[j]; if(j==k-1) ed=s[j]; } for(int j=st;j<=ed;j++){ if(!vis[j]){ for(int e=0;e<k;e++)//利用mp数组防止重边爆RE if(!mp[s[e]][j]) mp[s[e]][j]=true,p[j]++,add(s[e],j); } } } topsort(); for(int i=1;i<=n;i++) res=max(res,top[i]); cout<<res+1<<endl; //最大的一级也算一个 return 0; }
拓扑排序的典型应用:
//Nastya and Potions //https://codeforces.com/problemset/problem/1851/E #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+10; int n,m,res,p[N],money[N],ans[N],t,a[N],x; int e[N],ne[N],h[N],idx; bool vis[N]; void add(int a,int b){ e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void topsort() { queue<int>que; for(int i=1;i<=n;i++){ if(!p[i]) que.push(i); if(a[i]) ans[i]=a[i]; } while(!que.empty()){ int now=que.front(); que.pop(); for(int i=h[now];~i;i=ne[i]){ int j=e[i]; p[j]--,money[j]+=ans[now]; if(p[j]==0){ que.push(j); ans[j]=min(money[j],ans[j]); } } } } void solve() { cin>>n>>m; idx=0; for(int i=0;i<=n;i++) ans[i]=0,h[i]=-1,money[i]=0; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=0;i<m;i++) cin>>x,a[x]=0; for(int i=1;i<=n;i++){ int j,y; cin>>j; while(j--) cin>>y,add(y,i),p[i]++;//一定要注意怎么建边 } topsort(); for(int i=1;i<=n;i++) cout<<ans[i]<<" "; cout<<endl; } signed main() { cin>>t; while(t--){ solve(); } return 0; }
Tarjan(有向图联通缩点):
//有向图的强连通分量 //将有向环图变成有向无环图(DAG) // Tarjan算法求强连通分量(SCC) // 对每个点定义两个时间戳 // dfn[u]表示遍历到u的时间戳 // low[u]从u开始走,所能遍历到的最小时间戳是什么。 // u是其所在的强联通分量的最高点等价于dfn[u] == low[u] //tarjan算法模板 O(n+m) //由于当我们每次搜到最低点的时候,那个点一定是在整个连通分量里最小的点,此时把他加入数组 //依次循环我们会发现,数组从1-num是递增的顺序,也就是有序的DAG void tarjan(int u) { dfn[u]=low[u]=++times; _stack[++top]=u,vis[u]=true; for(int i=h[u];~i;i=ne[i]){ int j=e[i]; if(!dfn[j]) tarjan(j),low[u]=min(low[u],low[j]); else if(vis[j]) low[u]=min(low[u],dfn[j]); } if(dfn[u]==low[u]){ int y; ++scc_num; do{ y=stk[top--]; vis[y]=false,id[y]=scc_num,scc[scc_num].push_back(y); }while(y!=u) } }
Tarjan应用:
//受欢迎的牛 //https://www.luogu.com.cn/problem/P2341 #include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+10,M=1e5+10; int n,m,res,p[N],size[N],id[N]; int e[N],ne[N],h[N],idx,num; int _stack[N],dfn[N],low[N],_size[N]; int top,times,scc_num; bool vis[N]; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void tarjan(int u) { dfn[u]=low[u]=++times; _stack[++top]=u,vis[u]=true; for(int i=h[u];~i;i=ne[i]){ int j=e[i]; if(!dfn[j]) tarjan(j),low[u]=min(low[u],low[j]); else if(vis[j]) low[u]=min(low[u],dfn[j]); } if(low[u]==dfn[u]){ int y; ++scc_num; do{ y=_stack[top--]; vis[y]=false,id[y]=scc_num,_size[scc_num]++; }while(y!=u); } } signed main() { std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>n>>m; memset(h,-1,sizeof h); while(m--){ int a,b; cin>>a>>b; add(a,b); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);//缩点 for(int i=1;i<=n;i++) for(int j=h[i];~j;j=ne[j]){ int k=e[j],a=id[i],b=id[k]; if(a!=b) p[a]++;//统计出度 } for(int i=1;i<=scc_num;i++) if(!p[i]){ res+=_size[i],num++; if(num>1) return cout<<"0"<<endl,0; } cout<<res<<endl; return 0; }
// //缩点 // //https://www.luogu.com.cn/problem/P3387 //本题基本思想,将连通图用tarhan缩成DAG图然后跑一遍最长路就可以 //下面有几个注意点 #include<bits/stdc++.h> using namespace std; const int N=1e5+10; int n,m,res,p[N],dist[N],a[N]; int e[N],ne[N],h[N],idx,money[N]; //int _e[N],_ne[N],_h[N],_idx; int dfn[N],low[N],_stack[N],id[N]; int scc_num,times,top; vector<int>mp[N]; bool vis[N]; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void tarjan(int u) { low[u]=dfn[u]=++times; _stack[++top]=u,vis[u]=true; for(int i=h[u];~i;i=ne[i]){ int j=e[i]; if(!dfn[j]) tarjan(j),low[u]=min(low[u],low[j]); else if(vis[j]) low[u]=min(low[u],dfn[j]); } if(low[u]==dfn[u]){ int y; ++scc_num; do{ y=_stack[top--]; vis[y]=false,money[scc_num]+=a[y],id[y]=scc_num; //链表都是新得了,我们得到的DAG是图是全新的 //所以此时的变量时SCC_NUM,我们要开一个新的数组记录边权值,这里用money }while(y!=u); } } void topsort() { queue<int>que; for(int i=1;i<=scc_num;i++) if(!p[i]) que.push(i),dist[i]=money[i]; while(!que.empty()){ int now=que.front(); que.pop(); for(int i=0;i<mp[now].size();i++){ int j=mp[now][i]; p[j]--,dist[j]=max(dist[j],dist[now]+money[j]); if(p[j]==0) que.push(j); } } } int main() { cin>>n>>m; memset(h,-1,sizeof h); for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=m;i++){ int x,y; cin>>x>>y; add(x,y); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for(int i=1;i<=n;i++) for(int j=h[i];~j;j=ne[j]){ int k=e[j]; int x=id[i],y=id[k];//这里不是id[j],而是id[i],因为我们要求的是第i个点与其所有邻点是否相通 if(x!=y) mp[x].push_back(y),p[y]++; //注意点一,一定要重新再建一个链表,而且之前的那个链表不能重置 } topsort(); for(int i=1;i<=n;i++) res=max(res,dist[i]); cout<<res<<endl; return 0; }
//[ZJOI2007] 最大半连通子图 //https://www.luogu.com.cn/problem/P2272 #include<bits/stdc++.h> #define int long long #include<unordered_set> using namespace std; const int N=1e5+10,M=2e6+10; int n,m,res,dist[N],p[N],cnt[N],mod,a[N]; int e[M],ne[M],h[N],hs[N],idx,ans; int _stack[N],dfn[N],low[N],id[N]; int top,times,scc_num; bool vis[N]; unordered_set<int>s; void add(int h[],int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void tarjan(int u) { low[u]=dfn[u]=++times; vis[u]=true,_stack[++top]=u; for(int i=h[u];~i;i=ne[i]){ int j=e[i]; if(!dfn[j]) tarjan(j),low[u]=min(low[u],low[j]); else if(vis[j]) low[u]=min(low[u],dfn[j]); } if(low[u]==dfn[u]){ int y; ++scc_num; do{ y=_stack[top--]; vis[y]=false,id[y]=scc_num,a[scc_num]++; }while(y!=u); } } // (1) void topsort() // { // queue<int>que; // for(int i=1;i<=scc_num;i++) if(!p[i]) que.push(i),dist[i]=a[i],cnt[i]=1; // while(!que.empty()){ // int now=que.front(); que.pop(); // for(int i=hs[now];~i;i=ne[i]){ // int j=e[i]; // if(dist[j]<dist[now]+a[j]){ // dist[j]=dist[now]+a[j]; // cnt[j]=cnt[now]%mod; // } // else if(dist[j]==dist[now]+a[j]) cnt[j]=(cnt[j]+cnt[now])%mod; // p[j]--; // if(p[j]==0) que.push(j); // } // } // } // int dfs(int u) // { // if(dist[u]) return dist[u]; // for(int i=0;i<g[u].size();i++){ // int x=dfs(i); // if(dist[u]<x+a[i]){ // dist[u]=x+a[i]; // cnt[u]=cnt[i]%mod; // } // else if(dist[u]==x+a[i]) cnt[u]=(cnt[u]+cnt[i])%mod; // } // return dist[u]; // } signed main() { cin>>n>>m>>mod; memset(h,-1,sizeof h); memset(hs,-1,sizeof hs); while(m--){ int a,b; scanf("%lld%lld",&a,&b); add(h,a,b); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for(int i=1;i<=n;i++) for(int j=h[i];~j;j=ne[j]){ int k=e[j],x=id[i],y=id[k]; int hash=x*M+y; if(x!=y&&!s.count(hash)) p[y]++,add(hs,x,y),s.insert(hash); } // topsort(); // (2) spfa();//参考tarjan一章中,一样的道理跑一遍最长路就ok了 //(3) 由于缩点之后已经成为逆着的拓扑序了,所以直接倒着推一遍就可以了 // for(int i=scc_num;i;i--){ // if(!dist[i]) dist[i]=a[i],cnt[i]=1; // for(int j=hs[i];~j;j=ne[j]){ // int k=e[j]; // if(dist[k]<dist[i]+a[k]){ // dist[k]=dist[i]+a[k]; // cnt[k]=cnt[i]; // } // else if(dist[k]==dist[i]+a[k]) cnt[k]=(cnt[k]+cnt[i])%mod; // } // } for(int i=1;i<=scc_num;i++){ if(res<dist[i]) res=dist[i],ans=cnt[i]; else if(res==dist[i]) ans=(ans+cnt[i])%mod; } cout<<res<<endl<<ans; return 0; }
Tarjan求无向图连通(边连通,求桥(割边)):
//无向图的连通 //边的双连通 //Redundant Paths G //https://www.luogu.com.cn/problem/P2860 #include<bits/stdc++.h> using namespace std; const int N=1e5+10; int n,m,res,p[N]; int e[N],ne[N],h[N],idx; int dfn[N],low[N],_stack[N],id[N]; int top,dcc_num,times; bool vis[N],bridge[N]; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void tarjan(int u, int from) { low[u] = dfn[u] = ++times; // 设置节点u的深度和最低深度 _stack[++top] = u; // 将节点u入栈 for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; // j为当前遍历到的相邻节点 if (!dfn[j]) { // 如果相邻节点j还没有被访问过 tarjan(j, i); // 递归访问节点j low[u] = min(low[u], low[j]); // 更新节点u的最低深度 if (dfn[u] < low[j]) // 如果从节点j能够到达更深的地方 bridge[i] = bridge[i ^ 1] = true; // 标记边i为桥 } else if (i != (from ^ 1)) { //from^1是上一条边的反向边,也就是从父节点到当前节点的边 low[u] = min(low[u], dfn[j]); // 更新节点u的最低深度 } } if (low[u] == dfn[u]) { int y; ++dcc_num; // 新的双连通分量编号 do { y = _stack[top--]; // 从栈中弹出节点,构成一个新的双连通分量 id[y] = dcc_num; // 标记节点属于第dcc_num个双连通分量 } while (y != u); } } int main() { cin>>n>>m; memset(h,-1,sizeof h); for(int i=0;i<m;i++){ int a,b; cin>>a>>b; add(a,b),add(b,a); } tarjan(1,-1); for(int i=0;i<idx;i++) if(bridge[i]) p[id[e[i]]]++; for(int i=1;i<=dcc_num;i++) if(p[i]==1) res++: cout<<(res+1)/2;//记住,无方向图变成双连通分量所需要建的桥的数量为 res/2向上取整 return 0; }
//炸铁路 //https://www.luogu.com.cn/problem/P1656 //求桥的数量就可以 #include<bits/stdc++.h> using namespace std; const int N=1e5+10; int n,m,res,p[N]; int e[N],ne[N],idx,h[N]; int _stack[N],dfn[N],low[N],id[N]; int top,times,dcc_num; bool vis[N]; vector<pair<int,int>>g; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } bool cmp(pair<int,int>a,pair<int,int>b) { if(a.first==b.first) return a.second<b.second; else return a.first<b.first; } void tarjan(int u,int from) { low[u]=dfn[u]=++times; _stack[++top]=u; for(int i=h[u];~i;i=ne[i]){ int j=e[i]; if(!dfn[j]){ tarjan(j,i); low[u]=min(low[u],low[j]); if(dfn[u]<low[j]) vis[i]=vis[i^1]=true; } else if(i!=(from^1)) low[u]=min(low[u],dfn[j]); } if(low[u]==dfn[u]){ int y; ++dcc_num; do{ y=_stack[top--]; id[y]=dcc_num; }while(y!=u); } } int main() { cin>>n>>m; memset(h,-1,sizeof h); for(int i=0;i<m;i++){ int a,b; cin>>a>>b; add(a,b),add(b,a); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,0); for(int i=0;i<idx;i++) if(vis[i]) g.push_back({min(e[i],e[i^1]),max(e[i],e[i^1])}),vis[i]=vis[i^1]=false; sort(g.begin(),g.end(),cmp); for(auto i:g) cout<<i.first<<" "<<i.second<<endl; return 0; }