1、Floyd算法:数据较小情况,算出每两点的最短距离
for(int k=1;k<=n;k++){for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){mp[i][j]=min(mp[i][j],max(mp[i][k],mp[k][j]));//求从一点到另一点所有路的最大值}}}
2、Spfa算法:可计算一点到其他所有点最短距离,可以算负环
//二维数组保存数据Memset(dis,inf,sizeof(dis));void spfa(int a){queue<int>q;memset(vis,0,sizeof(vis));q.push(a);dis[a]=0;vis[a]=1;while(!q.empty()){int p=q.front();q.pop();vis[p]=0;for(int i=1; i<=n; i++){if(mp[p][i]!=inf){if(dis[i]>mp[p][i]+dis[p]){dis[i]=mp[p][i]+dis[p];if(vis[i]==0){q.push(i);vis[i]=1;}}}}}cout<<dis[n]<<endl;}//前向星保存数据void add(int u,int v,int w){e[cnt].to=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt++;}long long spfa(){memset(dis,dis+n,inf);memset(vis,0,sizeof(vis));queue<int>q;q.push(1);dis[1]=0;vis[1]=0;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=head[u];i!=-1;i++){if(dis[e[i].to]>dis[u]+e[i].w){dis[e[i].to]=dis[u]+e[i].w;if(!vis[e[i].to]){q.push(e[i].to);vis[e[i].to]=1;}}}}}
3、输出字典序最小的最短路(floyd用dis[ i ][ j ]数组保留路径,dis[ i ][ j ]=h表示从 i 到 j 是 i 先到 h 再到 j 的)
const int maxl=1010;const int inf=0x3f3f3f3f;int mp[maxl][maxl];int dis[maxl][maxl];int p[maxl];int main(){int n;while(scanf("%d",&n)&&n){memset(dis,0,sizeof(dis));for(int i=1; i<=n; i++){for(int j=1; j<=n; j++){dis[i][j]=j;scanf("%d",&mp[i][j]);if(mp[i][j]==-1)mp[i][j]=inf;}}for(int i=1; i<=n; i++)scanf("%d",&p[i]);for(int k=1; k<=n; k++){for(int i=1; i<=n; i++){for(int j=1; j<=n; j++){if(mp[i][j]>mp[i][k]+p[k]+mp[k][j]){mp[i][j]=mp[i][k]+p[k]+mp[k][j];dis[i][j]=dis[i][k];//i到j中间点就是k}if(mp[i][j]==mp[i][k]+p[k]+mp[k][j]){dis[i][j]=min(dis[i][j],dis[i][k]);//按字典序保存}}}}int a,b;while(scanf("%d%d",&a,&b)){if(a==-1&&b==-1)break;int c=a;int d=b;int pre[maxl];int cnt=0;while(a!=b){pre[cnt++]=dis[a][b];a=dis[a][b];}printf("From %d to %d :\n",c,d);printf("Path: %d",c);for(int i=0; i<cnt; i++)printf("-->%d",pre[i]);printf("\n");if(mp[c][d]<inf)printf("Total cost : %d\n",mp[c][d]);elseprintf("-1\n");printf("\n");}}return 0;}
4、加一条边,使得最短路最大
//给出一个由 n 个点和 m 条边组成的无向图,其中有 k 个点被标记了,题目要求选出两个被标记的点,连接一条边,使得从点 1 到点 n 的最短路最大。//题解:先来考虑一个比较简单的事情,假如现在有两个点 i 和 j ,如果其建边的话,最短路可能是 1 -> i -> j -> n 或者 1 -> j -> i -> n,这样代表的距离也就是 dis[ i ][ 0 ] + dis[ j ][ 1 ] + 1 和 dis[ i ][ 1 ] + dis[ j ][ 0 ] + 1 了,因为题目要求的是最短路,所以很显然会选择更小的那一个,换句话说,当满足 dis[ i ][ 0 ] + dis[ j ][ 1 ] + 1 < dis[ i ][ 1 ] + dis[ j ][ 0 ] + 1 时,我们会选择前者,通过移项以及约分,不难化简到:dis[ i ][ 0 ] - dis[ i ][ 1 ] < dis[ j ][ 0 ] - dis[ j ][ 1 ],这个公式又代表什么呢?也就是说,当我们现在有两个被标记的点时,dis[ i ][ 0 ] - dis[ i ][ 1 ] 更小的这个点会放在前面,而另外一个点会放在后面,这样我们O(n)枚举一遍位于后面的点 j ,然后找到点 j 前面的 dis[ i ][ 0 ] 的最大值,也就是前缀的最大值,这样可以保证相加之和是最大的,最后记得特判一下上限就好了,上限是原图的最短路。int id[N],dis[N][2];void bfs(int st,int pos){queue<int>q;q.push(st);dis[st][pos]=0;while(q.size()){int u=q.front();q.pop();for(int i=0; i<node[u].size(); i++){int to=node[u][i];if(dis[to][pos]>dis[u][pos]+1){dis[to][pos]=dis[u][pos]+1;q.push(to);}}}}bool cmp(int a,int b){return dis[a][0]-dis[a][1]<dis[b][0]-dis[b][1];}int main(){memset(dis,inf,sizeof(dis));int n,m,k;scanf("%d%d%d",&n,&m,&k);for(int i=1; i<=k; i++)scanf("%d",id+i);while(m--){int u,v;scanf("%d%d",&u,&v);node[u].push_back(v);node[v].push_back(u);}bfs(1,0);bfs(n,1);sort(id+1,id+k+1,cmp);int maxn=0,minx=0,ans=0;int flag=0;for(int i=2;i<=k;i++){ans=max(ans,min(dis[id[i-1]][0]+dis[id[i]][1]+1,dis[n][0]));}cout<<ans<<endl;return 0;}
5、统计最短路条数:
加一个way[]数组统计,每次更新dist时,way[to]=way[u]当dist相等时way[to]+=way[u]
//计算到每个点最短路的数量void djks()//djks算法{memset(dis,inf,sizeof(dis));memset(vis,0,sizeof(vis));memset(c,0,sizeof(c));dis[1]=0;for(int i=1;i<n;i++){int minx=inf;int k;for(int j=1;j<=n;j++){if(!vis[j]&&dis[j]<minx){minx=dis[j];k=j;}}vis[k]=1;for(int j=1;j<=n;j++){if(!mp[k][j]) continue;if(!vis[j]&&dis[j]>dis[k]+mp[k][j]){c[j]=0;dis[j]=dis[k]+mp[k][j];}if(dis[j]==dis[k]+mp[k][j]&&!vis[j])c[j]++;}}}void add(int a, int b, int c){str[cnt].to = b;str[cnt].w = c;str[cnt].next = head[a];head[a] = cnt++;}Spfa算法求void spfa(){for (int i = 1; i <= n; i++){vis[i] = 0;c[i] = 0;dis[i] = inf;}dis[1] = 0;c[1] = 1;queue<int>q;q.push(1);vis[1] = 1;while (!q.empty()){int p = q.front();q.pop();vis[p] = 0;for (int i = head[p]; i != -1; i = str[i].next){int to = str[i].to;if (dis[to] > dis[p] + str[i].w){dis[to] = dis[p] + str[i].w;if (!vis[to]){q.push(to);vis[to] = 1;}c[to] = c[p]%mod;}else if (dis[to] == dis[p] + str[i].w){c[to] = (c[to] + c[p]) % mod;}}}}
6、次短路与最短路计数
//我们可以记录最短路和短路的长度及其次数并不断更新他们。每次更新是无非5种情况:比最小值小,等于最小值,大于最小值小于次小值,等于次小值,大于次小值;using namespace std;const int maxl=1100;const int inf=0x3f3f3f3f;int vis[maxl][2];int ans[maxl][2];//记录最短路和次短路数量int dis[maxl][2];//记录每个点最短路和次短路距离int n,m,s,e,cnt;struct node{int to,next,w;} ve[maxl];int head[maxl*maxl];void add(int u,int v,int w){ve[cnt].to=v;ve[cnt].w=w;ve[cnt].next=head[u];head[u]=cnt++;}void djks(int s,int e){for(int i=0;i<=n;i++){dis[i][0]=dis[i][1]=inf;ans[i][0]=ans[i][1]=inf;}dis[s][0]=0;ans[s][0]=1;memset(vis,0,sizeof(vis));for(int i=1;i<=n*2;i++){int minx=inf,flag,k=-1;for(int j=0;j<n;j++){if(!vis[j][0]&&minx>dis[j][0]){minx=dis[j][0];flag=0;k=j;}else if(!vis[j][1]&&minx>dis[j][1]){minx=dis[j][1];flag=1;k=j;}}if(k==-1) break;vis[k][flag]=1;for(int j=head[k];j!=-1;j=ve[j].next){int to=ve[j].to;int w=ve[j].w;if(minx+w<dis[to][0])// 比最小值小{dis[to][1]=dis[to][0];//次小等于最小ans[to][1]=ans[to][0];dis[to][0]=minx+w;ans[to][0]=ans[k][flag];}else if(minx+w==dis[to][0]) ans[to][0]+=ans[k][flag];else if(minx+w==dis[to][1]) ans[to][1]+=ans[k][flag];else if(minx+w<dis[to][1]){dis[to][1]=minx+w;ans[to][1]=ans[k][flag];}}}cout<<dis[e][1]<<" "<<ans[e][1]<<endl;}int main(){while(cin>>n>>m>>s>>e){memset(head,-1,sizeof(head));cnt=0;int a,b,c;for(int i=0; i<m; i++){cin>>a>>b>>c;add(a,b,c);}djks(s,e);}return 0;}
7、求路径上最大值最小(最小路径最大值)的路径
//找每条路最小值,再求所有最小值的最大值(prim算法变形);void dijk(){memset(vis,0,sizeof(vis));for(int i=1; i<=n; i++)d[i]=mmp[1][i];d[1]=0;vis[0]=1;for(int i=1; i<=n; i++){int u=-1,m=0;for(int j=1; j<=n; j++){if(!vis[j]&&d[j]>m){m=d[j];u=j;}}vis[u]=1;for(int i=1; i<=n; i++){if(mmp[u][i])d[i]=max(d[i],min(d[u],mmp[u][i]));}}}
8、往返最短路,正反都跑一次最短路
using namespace std;const int N=1000000+10;const int INF=0x3f3f3f3f;struct edge{int to,w,nxt;};edge e[N];int n,cnt,head[N],a1[N],a2[N],c[N];long long dis[N];int vis[N];void add(int u,int v,int w){e[cnt].to=v;e[cnt].w=w;e[cnt].nxt=head[u];head[u]=cnt++;}long long spfa(){fill(dis,dis+n+1,INF);memset(vis,0,sizeof(vis));queue<int>q;q.push(1);dis[1]=0;vis[1]=1;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=head[u]; i!=-1; i=e[i].nxt){if(dis[e[i].to]>dis[u]+e[i].w){dis[e[i].to]=dis[u]+e[i].w;if(!vis[e[i].to]){q.push(e[i].to);vis[e[i].to]=1;}}}}long long res=0;for(int i=1; i<=n; ++i)res+=dis[i];return res;}int main(){int T,m;scanf("%d",&T);while(T--){cnt=0;scanf("%d%d",&n,&m);fill(head,head+n+1,-1);for(int i=0; i<m; ++i){scanf("%d%d%d",&a1[i],&a2[i],&c[i]);add(a1[i],a2[i],c[i]);}long long ans=spfa();cnt=0;fill(head,head+n+1,-1);for(int i=0; i<m; ++i)add(a2[i],a1[i],c[i]);ans+=spfa();printf("%lld\n",ans);}return 0;}
9、破坏路径最短路+打印最短路
//随机破坏一条路径,求最坏情况下的最短路,如果有影响破坏的路径一定在原先最短路上,先求出路径,再对每一条边分别拆掉跑一次最短路const int maxl=1000+10;const int inf=0x3f3f3f3f;struct node{int to,cost,id;node(int a,int b,int c):to(a),cost(b),id(c){}};bool vis[maxl],str[maxl*50],flag;int pre[maxl],dis[maxl],rode[maxl];int n,m;vector<node>ve[maxl];void spfa(){queue<int>q;q.push(1);memset(vis,0,sizeof(vis));for(int i=1;i<=n;i++) dis[i]=inf;dis[1]=0;vis[1]=1;while(!q.empty()){int p=q.front();q.pop();vis[p]=0;for(int i=0;i<ve[p].size();i++){int to=ve[p][i].to;int cost=ve[p][i].cost;int id=ve[p][i].id;if(str[id]) continue;//如果被破坏不能走if(dis[to]>dis[p]+cost){dis[to]=dis[p]+cost;//更新if(flag)//可以保存路径{rode[to]=id;//保存通向这个点的边pre[to]=p;//保存这个点的前驱}if(!vis[to]){q.push(to);vis[to]=1;}}}}}int main(){int t;scanf("%d",&t);while(t--){int a,b,c;scanf("%d%d",&n,&m);//点的个数,边的个数for(int i=1;i<=n;i++) ve[i].clear();for(int i=1;i<=m;i++){scanf("%d%d%d",&a,&b,&c);ve[a].push_back(node(b,c,i));//保存末位置,以及两点之间的权值以及每条边的标号ve[b].push_back(node(a,c,i));}memset(str,false,sizeof(str));//用str数组来标记某条边被破坏flag=true;//用来区分是保存路径还是开始美剧被破坏的路径spfa();flag=false;if(dis[n]==inf)//没有被破坏就无法到达目的地{printf("-1\n");continue;}int maxn=0;for(int i=n;i!=1;i=pre[i])//遍历最短路{str[rode[i]]=true;//标记这条路被破坏spfa();str[rode[i]]=false;//回溯if(dis[n]==inf)//破坏后无法到达{maxn=inf;break;}maxn=max(maxn,dis[n]);//保存破坏后最短路最大值}if(maxn==inf){printf("-1\n");}else{printf("%d\n",maxn);}}return 0;}
10、用spfa判断负环(虫洞问题,是否存在一条路有负环)
#define N 5210#define INF 0xfffffffint cnt, dist[N], Head[N], num[N], vis[N];int n, m, w;struct Edge{int v, w, next;}e[N];void Add(int u, int v, int w){e[cnt].v = v;e[cnt].w = w;e[cnt].next = Head[u];Head[u] = cnt++;}bool spfa()///spfa模板;{memset(vis, 0, sizeof(vis));memset(num, 0, sizeof(num));queue<int>Q;vis[1] = 1;dist[1] = 0;Q.push(1);num[1]++;while(Q.size()){int p=Q.front();Q.pop();vis[p] = 0;for(int i=Head[p]; i!=-1; i=e[i].next){int q = e[i].v;if(dist[q] > dist[p] + e[i].w){dist[q] = dist[p] + e[i].w;if(!vis[q]){vis[q] = 1;Q.push(q);num[q] ++;if(num[q]>=n)return true;}}}}return false;}int main(){int T, a, b, c;scanf("%d", &T);while(T--){scanf("%d%d%d", &n, &m, &w);cnt = 0;memset(Head, -1, sizeof(Head));for(int i=1; i<=n; i++)dist[i] = INF;for(int i=1; i<=m; i++){scanf("%d%d%d", &a, &b, &c);Add(a, b, c);Add(b, a, c);}for(int i=1; i<=w; i++){scanf("%d%d%d", &a, &b, &c);Add(a, b, -c);}if( spfa() )///存在负环;printf("YES\n");elseprintf("NO\n");}return 0;}
11、 单源最短路
struct Node{int to, lat, val; //边的右端点,边下一条边,边权};Node edge[1000005];int head[1005],tot,dis[1005],N,M,sb[1005],vis[1005];int pos[1005];void add(int from, int to, int dis){edge[++tot].lat = head[from];edge[tot].to = to;edge[tot].val = dis;head[from] = tot;}void spfa(int s){memset(dis, 0x3f, sizeof(dis));memset(vis, 0, sizeof(vis));vis[s] = 1;dis[s] = 0;queue<int>Q;Q.push(s);while (!Q.empty()) {int u = Q.front();Q.pop();vis[u] = 0;for (int i = head[u];i;i = edge[i].lat) {int to = edge[i].to;int di = edge[i].val;if (dis[to]>dis[u] + di) {dis[to] = dis[u] + di;if (!vis[to]) {vis[to] = 1;Q.push(to);}}}}}int main(){int val, e, s, k, c;int t, x;scanf("%d", &t);while (t--){memset(head, 0, sizeof(head));tot = 0;scanf("%d %d %d %d %d", &N, &e, &s, &k, &c);//点的个数、边的个数、英雄点、消防点的个数for (int i = 1; i <= k; ++i)//输入消防点,并和超级点连接{scanf("%d", &pos[i]);}while (e--){int a, b, dis;scanf("%d %d %d", &a, &b, &dis);add(a, b, dis),add(b,a,dis);}spfa(s);int ans2 = -1;for (int i = 1; i <= N; ++i) ans2 = max(ans2, dis[i]);for (int i = 1; i <= k; ++i){add(0, pos[i], 0);add(pos[i], 0, 0);}spfa(0);int ans1= -1;for (int i = 1; i <= N; ++i) ans1 = max(ans1, dis[i]);}return 0;}
12、通过建辅助点减少建边
//如果有两个城市群、将两个城市群的每个城市与另一个城市群的每个城市建边,暴力肯定超时,越界,给每个城市群建一个进出口(两个点)城市群城市群之间靠进出口联系,这样减少可时间和空间using namespace std;const int maxl=200000+10;const long long inf=1e18;int cnt,n,m;struct node{int to,next;long long cost;} str[maxl*3];int vis[maxl*3];long long dis[maxl*3];int head[maxl*3];void add(int v,int to,int cost){str[cnt].to=to;str[cnt].cost=cost;str[cnt].next=head[v];head[v]=cnt++;}void spfa(int st,int en){for(int i=1;i<=n+m+m;i++){dis[i]=1e18;将到所有点的距离设为无穷大vis[i]=0;}queue<int>q;vis[st]=1;dis[st]=0;q.push(st);while(!q.empty()){int p=q.front();q.pop();vis[p]=0;for(int i=head[p];i!=-1;i=str[i].next){int to=str[i].to;if(dis[to]>dis[p]+str[i].cost){dis[to]=dis[p]+str[i].cost;if(!vis[to]){vis[to]=1;q.push(to);}}}}if(dis[en]==inf){printf("-1\n");}else{printf("%lld\n",dis[en]);}}int main(){scanf("%d%d",&n,&m);//n个城市,m个城市群memset(head,-1,sizeof(head));cnt=0;int ans;for(int i=1; i<=m; i++){scanf("%d",&ans);//第i个城市群的城市个数int p;for(int j=1; j<=ans; j++){scanf("%d",&p);add(p,i+n,0);//将城市与出口连接add(i+n+m,p,0);//将城市与入口连接}}scanf("%d",&ans);//城市与城市之间边的个数int a,b,c;for(int i=1; i<=ans; i++){scanf("%d%d%d",&a,&b,&c);add(a,b,c);//建边add(b,a,c);}scanf("%d",&ans);//城市群和城市群之间的边数for(int i=1; i<=ans; i++){scanf("%d%d%d",&a,&b,&c);c代表权值add(a+n,b+n+m,c);//一个城市群的入口和另一个城市群的出口连接add(b+n,a+n+m,c);}int st,en;scanf("%d%d",&st,&en);spfa(st,en);return 0;}
13、树的重心( 树的重心也叫树的质心。找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡)
const int maxl=50005;int n;struct node{int to,next;}str[maxl*2];int head[maxl];int cnt;int sum[maxl];int dp[maxl];//int dis[maxl];void add(int x,int y){str[++cnt].to=y;str[cnt].next=head[x];head[x]=cnt;}//找重心,最长子树的节点最少void dfs(int fa,int u){sum[fa]=1;for(int i=head[fa];i!=-1;i=str[i].next){int to=str[i].to;if(to!=u){dfs(to,fa);sum[fa]+=sum[to];//记录每个子树的节点数dp[fa]=max(dp[fa],sum[to]);}}dp[fa]=max(dp[fa],n-sum[fa]);}//给每个节点赋值void spfa(int fa,int u){for(int i=head[fa];i!=-1;i=str[i].next){int to=str[i].to;if(to!=u){sum[to]=sum[fa]+1;//该题边权为1,不同的体需要更换为每个点对应的边权spfa(to,fa);}}}int main(){// int n;cin>>n;cnt=0;for(int i=0;i<=n;i++){head[i]=-1;sum[i]=0;}int x,y;for(int i=1;i<n;i++){cin>>x>>y;add(x,y);add(y,x);}dfs(1,1);int id=1;for(int i=2;i<=n;i++){if(dp[i]<dp[id])id=i;}sum[id]=0;spfa(id,id);long long ans=0;for(int i=1;i<=n;i++){ans+=sum[i];}cout<<id<<" "<<ans<<endl;return 0;}
14、最小生成树prim
int prim(){for(int i=0; i<n; i++){str1[i]=mp[0][i];}vis[0]=1;int sum=0;int k;for(int i=1; i<n; i++){int minx=inf;for(int j=0; j<n; j++){if(!vis[j]&&str1[j]<minx){k=j;minx=str1[j];}}vis[k]=1;sum+=minx;for(int j=0; j<n; j++){if(!vis[j]&&str1[j]>mp[k][j]){str1[j]=mp[k][j];}}}return sum;}int main(){cin>>n>>m;memset(head,-1,sizeof(head));int a,b,c;for(int i=0;i<m;i++){cin>>a>>b>>c;add(a,b,c);add(b,a,c);}prim();return 0;}
15、记录最小生成树路径上任意两点之间最长的边
void prim(){for(int i=1; i<=n; i++){dis[i]=mp[1][i];pre[i]=1;}vis[1]=1;dis[1]=0;double sum=0;int k;for(int i=1; i<n; i++){double minx=inf;for(int j=1; j<=n; j++){if(!vis[j]&&minx>dis[j]){minx=dis[j];k=j;}}sum+=minx;vis[k]=1;//保存最小生成树每条路径大小dp[pre[k]][k]=dp[k][pre[k]]=minx;for(int j=1; j<=n; j++){if(!vis[j]&&dis[j]>mp[k][j]){pre[j]=k;dis[j]=mp[k][j];}}//寻找最小生成树中每两个点之间最大边for(int j=1; j<=n; j++){if(vis[j]&&j!=k){dp[j][k]=max(dp[j][pre[k]],minx);dp[k][j]=dp[j][k];}}}double ans=0;for(int i=1; i<=n; i++){for(int j=1; j<=n; j++){if(i!=j){ans=max(ans,(double)(p[i]+p[j])/(sum-dp[i][j]));}}}cout<<fixed<<setprecision(2)<<ans<<endl;}
16、最小生成树唯一性,同时可以判断如果改变某一条边的值最小生成树是否会变化、
const int inf=0x3f3f3f3f;const int maxl=100+10;int n,m;int vis[maxl],mp[maxl][maxl],dis[maxl],pre[maxl];int use[maxl][maxl],maxn[maxl][maxl];int ret;void init(){memset(mp,inf,sizeof(mp));memset(vis,0,sizeof(vis));memset(use,0,sizeof(use));memset(maxn,0,sizeof(maxn));}void prim(){ret=0;for(int i=1; i<=n; i++){dis[i]=mp[1][i];pre[i]=1;}pre[1]=0;vis[1]=1;for(int i=1; i<n; i++){int minx=inf;int k;for(int j=1; j<=n; j++){if(!vis[j]&&dis[j]<minx){minx=dis[j];k=j;}}ret+=minx;vis[k]=1;use[pre[k]][k]=use[k][pre[k]]=1;//记录最小生成树边maxn[k][pre[k]]=maxn[pre[k]][k]=minx;//记录最小生成树边的权值for(int j=1; j<=n; j++){if(vis[j]&&j!=k){maxn[k][j]=maxn[j][k]=max(maxn[pre[k]][j],minx);//记录点和最小生成树其他点连接线上的最大权值}if(!vis[j]&&dis[j]>mp[k][j]){pre[j]=k;dis[j]=mp[k][j];}}}}int main(){int t;while(cin>>t){while(t--){int flag=1;init();cin>>n>>m;int a,b,c;for(int i=1; i<=m; i++){cin>>a>>b>>c;mp[a][b]=mp[b][a]=c;}prim();for(int i=1; i<=n; i++){for(int j=i+1; j<=n; j++){if(use[i][j]==1||mp[i][j]==inf)continue;if(maxn[i][j]==mp[i][j]){flag=0;break;}}if(flag==0)break;}if(flag){cout<<ret<<endl;}else{cout<<"Not Unique!"<<endl;}}}return 0;}
17、用并查集合并树中的节点(对边的排序处理)
//例题:给出N个点,它们之间有N−1条路径相连,其中K个特殊点,我们要删去一些边使得这K个点不连通,求最少的删除代价。const int maxl=100000+10;const int inf=0x3f3f3f3f;int father[maxl],vis[maxl];struct node{int x,y,w;} str[maxl];//记录边bool cmp(node a,node b){return a.w>b.w;}int getf(int x){if(father[x]==x)return x;return father[x]=getf(father[x]);//合并路径}//合并点集void cmbe(int a,int b){int t1=getf(a);int t2=getf(b);if(!vis[t1]&&vis[t2])father[t1]=t2;elsefather[t2]=t1;}int main(){int n,m;cin>>n>>m;for(int i=0; i<n; i++)father[i]=i;int a,b,c;memset(vis,0,sizeof(vis));for(int i=0; i<m; i++){cin>>a;vis[a]=1;//特殊点记录下来}for(int i=0; i<n-1; i++){cin>>str[i].x>>str[i].y>>str[i].w;}sort(str,str+n-1,cmp);long long ans=0;for(int i=0; i<n-1; i++){int x=str[i].x;int y=str[i].y;int w=str[i].w;if(vis[getf(x)]&&vis[getf(y)])ans+=w;elsecmbe(x,y);}cout<<ans<<endl;return 0;}
18、拓扑排序
void tuopu(){int k=0;queue<int>q;for(int i=1; i<=n; i++){if(str[i]==0)//入度为0{q.push(i);arr[i]=1;}}while(!q.empty()){int p=q.front();q.pop();vis[k++]=p;for(int i=1; i<=n; i++){if(mp[p][i]==1)str[i]--;if(str[i]==0&&arr[i]==0){q.push(i);arr[i]=1;}}}}
19、匈牙利算法最大匹配
#include<iostream>#include<string.h>#include<algorithm>#include<string>#include<cmath>using namespace std;const int maxl = 1000 + 10;int vis[maxl];int mach[maxl];int mp[maxl][maxl];int m, p;bool dfs(int u){for (int i = 1; i <= p; i++){if (vis[i] == 0 && mp[u][i]){vis[i] = 1;if (mach[i] == 0 || dfs(mach[i])){mach[i] = u;return true;}}}return false;}int main(){int n;while (cin >> n){if (n == 0)break;cin >> m >> p;int a, b;memset(mach, 0, sizeof(mach));memset(mp, 0, sizeof(mp));for (int i = 0; i < n; i++){cin >>a>>b;mp[a][b] = 1;}int ans = 0;for (int i = 1; i <= m; i++){memset(vis, 0, sizeof(vis));if (dfs(i))ans++;}cout << ans << endl;}return 0;}
拓展题
- 一个人在某一排选座位,每排有两个座位
const int maxl=4000+10;struct node{int to,next;} str[11000];int vis[maxl],mach[maxl][2];int head[maxl],cnt;void add(int a,int b){str[++cnt].to=b;str[cnt].next=head[a];head[a]=cnt;}bool dfs(int u){for(int i=head[u]; i!=-1; i=str[i].next){int to=str[i].to;if(!vis[to]){vis[to]=1;if(mach[to][0]==0||dfs(mach[to][0]))//二维数组作为两个座位{mach[to][0]=u;return true;}if(mach[to][1]==0||dfs(mach[to][1])){mach[to][1]=u;return true;}}}return false;}int main(){int n;//n排座位,2*n个人cin>>n;int a,b;cnt=0;memset(head,-1,sizeof(head));memset(mach, 0, sizeof(mach));for(int i=1; i<=2*n; i++){scanf("%d%d",&a,&b);add(i,a);//人对应座位add(i,b);}int ans=0;for(int i=1; i<=2*n; i++){memset(vis,0,sizeof(vis));if(dfs(i))ans++;}cout<<ans<<endl;return 0;}
- 有n个位数相同的数组,从每个数组中选出一个数,要求是连续递增,最长有多长。
const int maxl = 1000000 + 10;const int maxn = 10000 + 5;int vis[maxn];int mach[maxl],n;struct node{int to, next;}str[2*maxl];int head[2 * maxn];int cnt;void add(int x, int y){str[++cnt].to = y;str[cnt].next = head[x];head[x] = cnt;}bool dfs(int a){if (vis[a]) return false;//不能去找已经选完的点vis[a] = 1;//标记数字for (int i = head[a]; i != -1; i = str[i].next)//不需要去判断是否有关系{int to=str[i].to;if (mach[to] == 0 || dfs(mach[to])){mach[to] = a;return true;}}return false;}int main(){cin >> n;int a, b;memset(mach, 0, sizeof(mach));memset(head, -1, sizeof(head));cnt = 0;//反思想建表,一个数字对应多个数组,标记数字for (int i = 1; i <= n; i++){scanf("%d%d", &a, &b);add(a, i);add(b, i);}int ans = 0;for (int i = 1; i <= 10000; i++)//从小到大按顺序找{memset(vis, 0, sizeof(vis));if (dfs(i))ans++;elsebreak;}printf("%d\n", ans);return 0;}
20、 二分图判断
#include <bits/stdc++.h>using namespace std;const int maxn = 20010;vector<int> ve[maxn];int color[maxn], fa[maxn], n, m, vis[maxn],str[maxn],arr[maxn];bool dfs(int v,int c){color[v] = c;for(int i = 0;i < ve[v].size(); i++){//如果相邻的两个颜色相同不符合if(color[ve[v][i]] == c) return false;//如果相邻的两个颜色不同则给这个节点附上颜色,并且检查这个字节点的情况if(color[ve[v][i]] == 0 && !dfs(ve[v][i],-c)) return false;}return true;}int main() {int x, y, a, b;while(cin >> n >> m) {int flag = 0;memset(color, 0, sizeof(color));for(int i = 0; i <= n; i++) {ve[i].clear();}//输入存图for(int i = 1; i <= m; i++) {cin >> a >> b;ve[a].push_back(b);ve[b].push_back(a);}for(int i = 1; i <= n; i++) {if(color[i] == 0) {//如果不符合if(!dfs(i,1)) {color[i] = 1;flag = 1;break;}}}}return 0;}
21、最大流
const int maxn=2e5+10;const int inf=0x3f3f3f3f;struct node{int to,w,next;} e[maxn];int n,m,s,en;int dis[maxn];int head[maxn];int cnt;void add(int a,int b,int w){e[cnt].to=b;e[cnt].w=w;e[cnt].next=head[a];head[a]=cnt;cnt++;}bool bfs(int s,int en)//找增广路{memset(dis,0,sizeof(dis));dis[s]=1;queue<int>q;q.push(s);while(!q.empty()){int p=q.front();q.pop();for(int i=head[p]; i!=-1; i=e[i].next){int to=e[i].to;if(e[i].w>0&&!dis[to])//边的权不为0且没有被记录{dis[to]=dis[p]+1;//加一q.push(to);//入队}}}return dis[en]>0;//看最后边的权是否为0}int dfs(int s,int exp)//计算最大流{if(s==en) return exp;int add=0;for(int i=head[s]; i!=-1; i=e[i].next){int to=e[i].to;if(dis[to]!=dis[s]+1||e[i].w<=0) continue;int x=dfs(to,min(exp-add,e[i].w));if(!x){dis[to]=-1;continue;}if(x){e[i].w-=x;e[i^1].w+=x;//建反边add+=x;}}Return add;}int main(){scanf("%d%d%d%d",&n,&m,&s,&en);int x,y,w;memset(head,-1,sizeof(head));// memset(dis,0,sizeof(dis));for(int i=1; i<=m; i++){scanf("%d%d%d",&x,&y,&w);add(x,y,w);add(y,x,0);}cnt=0;int ans=0;while(bfs(s,en))//存在增广路{ans+=dfs(s,inf);}printf("%d\n",ans);return 0;}
22、二分时间+最大流
//题目描述:若干个石油起点汇点,每个起点有一定的储存石油,每个汇点有一定的石油需求,每个起点可以通向多个汇点,但都要相应的时间,现在要以最短的时间满足汇点的需求,可以采用二分时间+最大流,建一个超级起点超级汇点、起点连接石油起点,边权为石油储存量、终点连接汇点,边权为需求量,起点和汇点之间连接按照是否满足时间来确定是否连接,边权为无穷大。const int inf=0x3f3f3f3f;const int maxl=1000000+10;struct node{int u,to,next,w;} ve[maxl*2];struct point{int u,v,w;} e[maxl*2];int cnt,head[maxl];int dis[maxl],st,en;int str[maxl],arr[maxl];void add(int u,int v,int w){ve[cnt].u=u;ve[cnt].to=v;ve[cnt].next=head[u];ve[cnt].w=w;head[u]=cnt++;}bool bfs(int s,int en){queue<int>q;memset(dis,0,sizeof(dis));dis[s]=1;q.push(s);while(!q.empty()){int p=q.front();q.pop();for(int i=head[p]; i!=-1; i=ve[i].next){int to=ve[i].to;if(ve[i].w>0&&!dis[to]){dis[to]=dis[p]+1;q.push(to);}}}return dis[en]>0;}int dfs(int s,int exp){if(s==en) return exp;int add=0;for(int i=head[s]; i!=-1&&add<exp; i=ve[i].next){int to=ve[i].to;if(dis[to]!=dis[s]+1||ve[i].w<=0) continue;int x=dfs(to,min(exp-add,ve[i].w));if(!x){dis[to]=-1;continue;}if(x){ve[i].w-=x;ve[i^1].w+=x;add+=x;}}return add;}int ans,sum;int p,r,c;void dinic(){while(bfs(st,en)) ans+=dfs(st,inf);}对每次时间建图,看最大流是不是满最大流bool check(int time){memset(ve,0,sizeof(ve));memset(head,-1,sizeof(head));memset(dis,0,sizeof(dis));ans=0;st=0;en=p+r+10;for(int i=1; i<=r; i++) add(st,i,arr[i]),add(i,st,0);for(int i=1; i<=p; i++) add(i+r,en,str[i]),add(en,i+r,0);for(int i=1; i<=c; i++){if(time>=e[i].w){add(e[i].v,e[i].u+r,inf);add(e[i].u+r,e[i].v,0);}}dinic();if(ans==sum) return true;return false;}int main(){memset(head,-1,sizeof(head));scanf("%d%d%d",&p,&r,&c);for(int i=1; i<=p; i++){scanf("%d",&str[i]);sum+=str[i];}for(int i=1; i<=r; i++)scanf("%d",&arr[i]);for(int i=1; i<=c; i++)scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);int l=0,r=1e7+1;bool flag=false;int x=0;while(l<=r){int mid=(l+r)/2;if(check(mid)){r=mid-1;x=mid;flag=1;}else{l=mid+1;}}if(!flag) cout<<-1<<endl;elsecout<<x<<endl;return 0;}
23、最小割(通过删除边让两点不能形成通路且权值最小)
#include<iostream>#include<cstdio>#include<algorithm>#include<queue>#include<cstring>#include<string>#include<map>using namespace std;const int maxn=5000+10;const int maxm=200000+5;const int inf=0x3f3f3f3f;int que[maxn];int dep[maxn],cur[maxn],str[maxn];struct Edge{int to,next,w,flow;} edge[maxm];int cnt;int head[maxn];void init(){cnt=2;memset(head,-1,sizeof(head));}void add(int u,int v,int w){edge[cnt].to=v;edge[cnt].w=w;edge[cnt].flow=0;edge[cnt].next=head[u];head[u]=cnt++;edge[cnt].to=u;edge[cnt].w=0;edge[cnt].flow=0;edge[cnt].next=head[v];head[v]=cnt++;}bool bfs(int s,int t,int n){int header=0,tail=0;memset(dep,-1,sizeof(dep));dep[s]=0;que[tail++]=s;while(header<tail){int u=que[header];header++;for(int i=head[u]; i!=-1; i=edge[i].next){int v=edge[i].to;if(edge[i].w>edge[i].flow&&dep[v]==-1){dep[v]=dep[u]+1;if(v==t) return true;que[tail++]=v;}}}return false;}int dinic(int s,int t,int n){int maxflow=0;while(bfs(s,t,n)){for(int i=0; i<n; i++) cur[i]=head[i];int u=s,tail=0;while(cur[s]!=-1){if(u==t){int sum=inf;for(int i=tail-1; i>=0; i--){sum=min(sum,edge[str[i]].w-edge[str[i]].flow);}maxflow+=sum;for(int i=tail-1; i>=0; i--){edge[str[i]].flow+=sum;edge[str[i]^1].flow-=sum;if(edge[str[i]].w-edge[str[i]].flow==0) tail=i;}u=edge[str[tail]^1].to;}else if(cur[u]!=-1&&edge[cur[u]].w>edge[cur[u]].flow&&dep[u]+1==dep[edge[cur[u]].to]){str[tail++]=cur[u];u=edge[cur[u]].to;}else{while(u!=s&&cur[u]==-1) u=edge[str[--tail]^1].to;cur[u] = edge [cur[u]].next;}}}return maxflow;}int main(){int t;scanf("%d",&t);while(t--){init();int n,m,k;cin>>n>>m>>k;int start=0,End=2*n+1;int u,v,w;for(int i=1; i<=m; i++){cin>>u>>v>>w;add(u+n,v,w);}for(int i=1; i<=n; i++){cin>>w;add(start,i,w);}for(int i=1; i<=n; i++){cin>>w;add(i,i+n,w);}add(k+n,End,inf);cout<<dinic(start,End,2*n+1)<<endl;}return 0;}
24、最小费用最大流
#define ll long long#define INF 0x3f3f3f3fconst int maxn = 1e5+5;using namespace std;struct node{int to;int c;int cost;int next;} e[maxn];int head[maxn],cnt,l,r,n,m;int maxflow,mincost;bool vis[maxn];int dis[maxn],pre[maxn];void insert(int u,int v,int c,int w){e[cnt].to = v;e[cnt].c = c;e[cnt].cost = w;e[cnt].next = head[u];head[u] = cnt++;}void addedge(int u,int v,int c,int w){insert(u,v,c,w);insert(v,u,0,-w);}bool SPFA(int s,int t){memset(vis,0,sizeof(vis));memset(pre,-1,sizeof(pre));memset(dis,0x3f3f3f3f,sizeof(dis));queue<int> q;q.push(s);vis[s]=1;dis[s]=0;while(!q.empty()){int x = q.front();q.pop();vis[x] = 0;for(int i = head[x]; i!=-1; i = e[i].next){int to = e[i].to;if(e[i].c && dis[to] > dis[x] + e[i].cost){dis[to] = dis[x] + e[i].cost;pre[to] = i;if(!vis[to]){vis[to] = 1;q.push(to);}}}}return pre[t] != -1;}void MCMF(int s,int t){int ans = 0;while(SPFA(s,t)){int flow = INF;for(int i = t; i != s; i = e[pre[i]^1].to){flow = min(flow, e[pre[i]].c);}maxflow+=flow;for(int i=t; i!=s; i = e[pre[i]^1].to){e[pre[i]].c-=flow;e[pre[i]^1].c+=flow;mincost += e[pre[i]].cost*flow;}}}int main(){int n,m,s,en;scanf("%d%d%d%d", &n, &m, &s, &en);memset(head, -1, sizeof(head));cnt = 0;int x, y, w, cost;for (int i = 1; i <= m; i++){scanf("%d%d%d%d", &x, &y, &w, &cost);addedge(x, y, w, cost);}MCMF(s,en);cout << maxflow << " " << mincost << endl;return 0;}
25、km算法(带权二分图的最优匹配,最大效率问题)
//男女匹配问题,每个女生对男生都有一定的好感度,求最大和好感度(求最小匹配,将权值改为负数,最后答案输出绝对值)using namespace std;const int inf=0x3f3f3f3f;const int maxl=310;int n;int mp[maxl][maxl];//记录好感度int vis[maxl],vb[maxl];//记录每一轮匹配锅的男生和女生int eb[maxl],eg[maxl];//每个男生女生的期望值int math[maxl];//能否匹配int slack[maxl];//记录男生能与女生匹配需要最少期望值int dfs(int id){vis[id]=1;for(int i=0; i<n; i++){if(vb[i]) continue;//每个男生只尝试一次int sum=eg[id]+eb[i]-mp[id][i];if(sum==0){vb[i]=1;if(math[i]==-1||dfs(math[i])) // 找到一个没有匹配的男生 或者该男生的妹子可以找到其他人{math[i]=id;return 1;}}else{slack[i]=min(slack[i],sum);}}return 0;}void km(){memset(math,-1,sizeof(math));//没有匹配memset(eb,0,sizeof(eb));//每个男生期望值为0// 每个女生的初始期望值是与她相连的男生最大的好感度for(int i=0; i<n; i++){eg[i]=mp[i][0];for(int j=1; j<n; j++)eg[i]=max(mp[i][j],eg[i]);}// 尝试为每一个女生解决归宿问题for(int i=0; i<n; i++){memset(slack,inf,sizeof(slack)); // 因为要取最小值 初始化为无穷大while(1){// 为每个女生解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止// 记录每轮匹配中男生女生是否被尝试匹配过memset(vis,0,sizeof(vis));memset(vb,0,sizeof(vb));int d=inf;if(dfs(i)) break;//找到退出for(int i=0;i<n;i++) if(!vb[i]) d=min(d,slack[i]);for(int i=0; i<n; i++){// 所有访问过的女生降低期望值if(vis[i]) eg[i]-=d;// 所有访问过的男生增加期望值if(vb[i]) eb[i]+=d;else slack[i]-=d;}}}}int main(){while(cin>>n){memset(mp,0,sizeof(mp));for(int i=0; i<n; i++){for(int j=0; j<n; j++){scanf("%d",&mp[i][j]);}}km();int ans=0;for(int i=0; i<n; i++){ans+=mp[math[i]][i];}cout<<ans<<endl;}return 0;}//Km算法优化using namespace std;typedef long long ll;const int MAX = 450;int n;ll b[MAX],c[MAX];ll d[MAX];int sum[MAX];struct node{ll num;int val;}a[MAX];int mp[MAX][MAX];int lx[MAX],ly[MAX],match[MAX],slack[MAX];int pre[MAX];bool vis[MAX];void dfs(int root){fill(vis+1,vis+1+n,false);fill(slack+1,slack+1+n,INF);int p;match[p=0] =root;do{vis[p]=true;int x=match[p],yy;int delta = INF;for(int i=1; i<=n; i++){if(!vis[i]){if(lx[x]+ly[i]-mp[x][i]<slack[i])slack[i] = lx[x]+ly[i]-mp[x][i],pre[i]=p;if(slack[i]<delta) delta=slack[i],yy=i;}}for(int i=0; i<=n; i++){if(vis[i])lx[match[i]] -= delta,ly[i] += delta;elseslack[i] -= delta;}p=yy;}while(match[p]!= -1);do{int pr = pre[p];match[p] = match[pr],p=pr;}while(p);}void km(){for(int i=1; i<=n; i++){lx[i]=ly[i]=0;match[i]=-1;for(int j=1; j<=n; j++){lx[i]=max(lx[i],mp[i][j]);}}int ans =0;for(int i = 1; i<=n; i++) dfs(i);for(int i=1; i<=n; i++) ans += lx[i],ans += ly[i];printf("%d\n",ans);}bool cmp(node a,node b){return a.num<b.num;}int main(){scanf("%d",&n);for(int i=1; i<=n; i++){scanf("%lld",&a[i].num);}for(int i=1; i<=n; i++){scanf("%d",&a[i].val);}for(int i=1; i<=n; i++){scanf("%lld",&b[i]);}for(int i=1; i<=n; i++){scanf("%lld",&c[i]);}sort(b+1,b+1+n);sort(c+1,c+1+n);sort(a+1,a+1+n,cmp);sum[0]=0;for(int i=1; i<=n; i++){sum[i] = sum[i - 1] + a[i].val;d[i]=a[i].num;}//存边for(int i=1; i<=n; i++){int last=1;for(int j=1; j<=n; j++){ll val = b[i] + c[j];while(d[last] < val&&last<=n) last++;mp[i][j]=sum[last-1];}}km();return 0;}
26、强连通图(tarjan算法)
const int maxl=100000+10;int head[maxl],dfn[maxl],low[maxl],fa[maxl],num[maxl],out[maxl];//dfn记录每个点被访问的顺序,low记录这个点前面最小的前序,fa记录该点所属的连通图,num记录//每个连通图有几个点,out记录每个连通图的出度bool vis[maxl];//是否在栈内stack<int>sta;int cnt,idx,sum,n,m;struct node{int to,next;} ve[maxl];void add(int u,int v){ve[++cnt].to=v;ve[cnt].next=head[u];head[u]=cnt;}void tarjan(int u){low[u]=dfn[u]=++idx;vis[u]=1;//入栈标记sta.push(u);for(int i=head[u]; i!=-1; i=ve[i].next){int to=ve[i].to;if(!dfn[to]){tarjan(to);low[u]=min(low[u],low[to]);//取最小前序}else if(vis[to])//形成回路{low[u]=min(low[u],dfn[to]);}}if(dfn[u]==low[u])//组成连通图{int v;sum++;//遍历连通图中的点do{v=sta.top();fa[v]=sum;vis[v]=0;num[sum]++;sta.pop();}while(v!=u);}}void init(){memset(head,-1,sizeof(head));memset(vis,0,sizeof(vis));memset(dfn,0,sizeof(dfn));cnt=0;sum=0;idx=0;memset(fa,0,sizeof(fa));memset(low,0,sizeof(low));memset(num,0,sizeof(num));}int main(){cin>>n>>m;int a,b;init();for(int i=0; i<m; i++){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=head[i]; j!=-1; j=ve[j].next){int to=ve[j].to;if(fa[i]!=fa[to]) out[fa[i]]++;}}//统计被其他n-1个牛欣赏的牛的个数int ans=0;for(int i=1; i<=sum; i++){if(!out[i]){if(ans){ans=0;break;}ans=num[i];}}cout<<ans<<endl;return 0;}
