能够结合这篇博客进行复习:http://www.cnblogs.com/z360/p/7363034.htmlphp
1、强连通份量、缩点html
习题:node
传送门ios
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 1010 using namespace std; bool vis[N]; int n,m,s1,s2,tot,tim,sum,top,ans1,ans2; int in[N],out[N],low[N],dfn[N],head[N],stack[N],belong[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int tarjan(int x) { dfn[x]=low[x]=++tim; stack[++top]=x,vis[x]=true; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(vis[t]) low[x]=min(low[x],dfn[t]); else if(!dfn[t]) tarjan(t),low[x]=min(low[x],low[t]); } if(low[x]==dfn[x]) { sum++;belong[x]=sum; for(;stack[top]!=x;top--) { vis[stack[top]]=false; belong[stack[top]]=sum; } top--,vis[x]=false; } } int shink_point() { for(int i=1;i<=n;i++) for(int j=head[i];j;j=edge[j].next) { int t=edge[j].to; if(belong[i]!=belong[t]) in[belong[t]]++,out[belong[i]]++; } } int main() { n=read(); for(int i=1;i<=n;i++) { while(1) { m=read(); if(m==0) break; add(i,m); } } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); shink_point(); for(int i=1;i<=sum;i++) { if(in[i]==0) s1++; if(out[i]==0) s2++; } if(sum==1) ans2=0; else ans2=max(s1,s2); ans1=s1; printf("%d\n%d",ans1,ans2); return 0; }
小总结:1.最少让几我的知道就能够作到让全部的人都知道信息,最少知道的人的数目即为缩完点后入读为零的点的个数算法
2.最少加入几条边就可使这个图变成一个强连通图,加的边的条数即为缩完点后Max(入读为零的点的个数,出度为零的点的个数)数组
poj——3177 Redundant Paths网络
传送门ide
求一个无向图在加入多少条边后变成边双连通图。跟上面的类型差很少相同,只不过是一个为有向图,一个为无向图。既然类型相同,那么咱们采用相同的思想来作这道题,首先咱们先tarjan缩点,而后统计入读为0点的点的个数。有同窗确定要问了,无向图啊,怎么可能入读为0呢,并且你怎么知道是入读仍是初读的啊,而且tarjan缩点的前提不是有向图吗?这是无向图啊,怎么缩点? 这就要归功于:if(i==(1^pre)) continue; 对,他是用两条边,可是咱们这个地方只让他走一条边,这样不就和有向图缩点同样了吗?!不知道是入读仍是出度,那么直接统计度数为2的点的个数。函数
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 5005 using namespace std; bool vis[N]; long long n,m,x,y,ans,tot=1,tim,sum,top; long long du[N],dfn[N],low[N],stack[N],belong[N]; long long head[20010]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();} while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();} return x*f; } struct Edge { int from,next,to; }edge[20010]; void add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int tarjan(int now,int pre) { dfn[now]=low[now]=++tim; stack[++top]=now; for(int i=head[now];i;i=edge[i].next) { int t=edge[i].to; if(i==(1^pre)) continue; if(!dfn[t]) tarjan(t,i),low[now]=min(low[now],low[t]); else low[now]=min(low[now],dfn[t]); } if(low[now]==dfn[now]) { sum++; belong[now]=sum; for(;stack[top]!=now;top--) belong[stack[top]]=sum; top--; } } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) x=read(),y=read(),add(x,y),add(y,x); tarjan(1,0); for(int i=1;i<=n;i++) for(int j=head[i];j;j=edge[j].next) if(belong[i]!=belong[edge[j].to]) du[belong[i]]++,du[belong[edge[j].to]]++; for(int i=1;i<=n;i++) if(du[i]==2) ans++; printf("%d",(ans+1)>>1); return 0; }
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 10010 using namespace std; bool vis[N]; int n,m,x,y,tot,tim,sum,top; int s[N],dfn[N],low[N],head[N],stack[N],belong[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int tarjan(int x) { dfn[x]=low[x]=++tim; stack[++top]=x;vis[x]=true; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(vis[t]) low[x]=min(low[x],dfn[t]); else if(!dfn[t]) tarjan(t),low[x]=min(low[x],low[t]); } if(low[x]==dfn[x]) { sum++;belong[x]=sum,s[sum]++; for(;stack[top]!=x;top--) { s[sum]++; vis[stack[top]]=false; belong[stack[top]]=sum; } vis[x]=false;top--; } } int main() { freopen("messagez.in","r",stdin); freopen("messagez.out","w",stdout); n=read(),m=read(); for(int i=1;i<=m;i++) { x=read(),y=read(); add(x,y); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for(int i=1;i<=n;i++) if(s[belong[i]]==1) printf("F\n"); else printf("T\n"); return 0; }
tarjan判环,能够用来解决如下问题 1.一我的发出的信息是否能传给本身 2.一群人玩游戏,若是本身的信息被别人告诉那么游戏结束,问最少进行的轮数,用tarjan求最小环中的点的个数
题解:咱们知道割掉一个点后,可以对整张图的不连通有序对形成影响的必定为割点,所以,咱们用tarjan处理,咱们将这个图当作一颗树,将点割掉之后不连通的有序对为这个点子树内的点的·个数*这个点父亲里面的点的个数。这样咱们又能得知他们之间不能相连的点数就是他的父亲节点内的个数*塔子树节点内的个数、。即ans【i】=t*(n-t-1) t=size[i] 这样咱们求出了从该点外不能互相到达的对数。最后再加上因为这个点割去了,所以这个点不能到达全部的点。最后乘2
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 500010 #define ll long long using namespace std; ll ans[N]; int n,m,x,y,tot,tim; int dfn[N],low[N],head[N],size[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,next; }edge[N<<1]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int tarjan(int x) { int z=0;size[x]=1; dfn[x]=low[x]=++tim; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(!dfn[t]) { tarjan(t);size[x]+=size[t]; low[x]=min(low[x],low[t]); if(dfn[x]<=low[t]) { ans[x]+=(ll)z*size[t]; z+=size[t]; } } else low[x]=min(low[x],dfn[t]); } ans[x]+=(ll)z*(n-z-1); } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { x=read(),y=read(); add(x,y),add(y,x); } tarjan(1); for(int i=1;i<=n;i++) printf("%lld\n",(ans[i]+n-1)<<1); return 0; }
最受欢迎的牛类的类的题目,咱们须要tarjan缩点而后初度为零的点即为最受欢迎的牛
2、割边、割点
习题:
题解:咱们要求最少修建几个逃生处才能使任意一个地方炸了全部的人都可以逃生。咱们假设将割点炸掉之后,这个图中必定会变成好几个双连通份量,咱们能够知道若是一个双连通份量中没有割点,那么咱们须要建两个逃生处扎了一个还能去另外一个;若是链接一个割点,咱们只须要在双连通图中建一个就行了,炸了割点还能逃生;若是连接两个,那个不管炸那个咱们都能经过另外一个逃生
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 510 using namespace std; long long ans2; bool vis[N],cut_point[N],cut_edge[N]; int head[N<<1],dfn[N],low[N],belong[N]; int n,m,x,y,s,tim,cut,tot,sum,cnt,ans1; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,next; }edge[N<<1]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int tarjan(int x,int pre) { int sum=0;bool boo=false; dfn[x]=low[x]=++tim; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if((1^i)==pre) continue; if(!dfn[t]) { sum++;tarjan(t,i); low[x]=min(low[x],low[t]); if(low[t]>dfn[x]) cut_edge[i/2]=true; if(low[t]>=dfn[x]) boo=true; } else low[x]=min(low[x],dfn[t]); } if(pre==-1){if(sum>1) cut_point[x]=true;} else if(boo) cut_point[x]=true; } int begin() { n=tim=ans1=0;tot=ans2=1; for(int i=1;i<=N;i++) { vis[i]=cut_point[i]=0; dfn[i]=low[i]=belong[i]=0; } memset(head,0,sizeof(head)); memset(edge,0,sizeof(edge)); } int dfs(int x) { s++;vis[x]=true;belong[x]=sum; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(cut_point[t]&&belong[t]!=sum) cut++,belong[t]=sum; if(!vis[t]&&!cut_point[t]) dfs(t); } } int main() { while(1) { m=read(); if(m==0) break;begin(); for(int i=1;i<=m;i++) { x=read(),y=read(); add(x,y),add(y,x); n=max(n,max(x,y)); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,-1); sum=0; for(int i=1;i<=n;i++) { if(vis[i]||cut_point[i]) continue; s=cut=0,sum++;dfs(i); if(!cut) ans1+=2,ans2*=(long long)(s*(s-1))>>1; if(cut==1) ans1++,ans2*=(long long)s; } printf("Case %d: %d %lld\n",++cnt,ans1,ans2); } return 0; }
当损坏一个点或一条边就不能是图联通的时候,就要求割点割边
求删除这个割点后有几个强连通子图
解决这一类问题的时候咱们能够先求出割点,咱们有知道low值相同的点在一个强连通份量里,而后咱们能够求与该割点相连的点的low值不一样的个数
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 10010 using namespace std; int n,x,y,tot,tim,cnt,sum; int dfn[N],low[N],head[N]; bool flag,vis[N],cut_point[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int from,next,to; }edge[N*200]; void add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int begin() { tim=0,tot=1;flag=false; memset(head,0,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(cut_point,0,sizeof(cut_point)); } int tarjan(int x,int pre) { int s=0; bool boo=false; dfn[x]=low[x]=++tim; for(int i=head[x];i;i=edge[i].next) { if((i^1)==pre) continue; int t=edge[i].to; if(!dfn[t]) { s++,tarjan(t,i); low[x]=min(low[t],low[x]); if(dfn[x]<=low[t]) boo=true; } else low[x]=min(low[x],dfn[t]); } if(pre==-1){if(s>1) cut_point[x]=true;} else if(boo) cut_point[x]=true; } int main() { while(1) { x=read();if(x==0) break; begin();y=read(),add(x,y),add(y,x); n=max(n,max(x,y)),cnt++; while(1) { x=read(); if(x==0) break; y=read(),add(x,y),add(y,x); n=max(n,max(x,y)); } printf("Network #%d\n",cnt); tarjan(1,-1); for(int i=1;i<=n;i++) if(cut_point[i]) { sum=0;flag=true; memset(vis,0,sizeof(vis)); for(int j=head[i];j;j=edge[j].next) { int t=edge[j].to; if(vis[low[t]]) continue; vis[low[t]]=true,sum++; } printf(" SPF node %d leaves %d subnets\n",i,sum); } if(!flag) printf(" No SPF nodes\n"); printf("\n"); } return 0; }
3、最小生成树
题解:要将这n各部落所有链接起来须要n-1条边,咱们要将它划分红m个部落,也就是说咱们要有m个部落不能被链接起来,咱们要先将距离小的两个部落先连起来,这就刚好是最小生成树的思想,因此要想到最小生成树也不难。咱们将这m个部落连起来刚好是用m-1条边,也就是说咱们将除了那m个部落连起来,刚好是要用n-1-(m-1)也就是n-m条边,而后这m部落间的最小的距离就是n-m+1条边的长度了
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 1010 using namespace std; double ans; int n,k,x,y,s,fx,fy,sum,fa[N],xx[N],yy[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int x,y; double z; }edge[N*N]; int cmp(Edge a,Edge b) {return a.z<b.z;} int find(int x) { if(x==fa[x]) return x; fa[x]=find(fa[x]); return fa[x]; } int main() { n=read(),k=read(); for(int i=1;i<=n;i++) xx[i]=read(),yy[i]=read(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i!=j) { ++s; edge[s].x=i; edge[s].y=j; edge[s].z=sqrt((double)pow(xx[i]-xx[j],2)+(double)pow(yy[i]-yy[j],2)); } for(int i=1;i<=n;i++) fa[i]=i; sort(edge+1,edge+1+s,cmp); for(int i=1;i<=s;i++) { x=edge[i].x,y=edge[i].y; fx=find(x),fy=find(y); if(fa[fx]==fy) continue; fa[fx]=fy;sum++; if(sum==n-k+1) {ans=edge[i].z;break;} } printf("%.2lf",ans); return 0; }
题解:现选k种A道路,而后在跑最小生成树,求最小生成树最长边
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 210000 using namespace std; int n,m,x,y,k,z1,z2,fx,fy,sum,ans,fa[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int x,y,z1,z2; }edge[N]; int cmp1(Edge a,Edge b) {return a.z1<b.z1;} int cmp2(Edge a,Edge b) {return a.z2<b.z2;} int find(int x) { if(x==fa[x]) return x; fa[x]=find(fa[x]); return fa[x]; } int main() { freopen("hzoi_road2.in","r",stdin); freopen("hzoi_road2.out","w",stdout); n=read(),k=read(),m=read(); for(int i=1;i<m;i++) { x=read(),y=read(),z1=read(),z2=read(); edge[i].x=x; edge[i].y=y; edge[i].z1=z1; edge[i].z2=z2; } for(int i=1;i<=n;i++) fa[i]=i; sort(edge+1,edge+m+1,cmp1); for(int i=1;i<=m;i++) { if(k==0) break; x=edge[i].x,y=edge[i].y; fx=find(x),fy=find(y); fa[fx]=fy;sum++; ans=max(ans,edge[i].z1); if(sum==k)break; } sort(edge+1,edge+1+m,cmp2); for(int i=1;i<=m;i++) { x=edge[i].x,y=edge[i].y; fx=find(x),fy=find(y); if(fx==fy) continue; fa[fx]=fy; ans=max(ans,edge[i].z2); } printf("%d",ans); return 0; }
题解:看到这个题首先想到的即是tarjan缩点而后求最小生成树吧,可是咱们会发现wa掉,为何,由于咱们这个地方是有向图,若是采用最小生成树使他们在一个并查集里,可是事实上这几个点可能不连通,因此咱们用贪心的思想,求出于每一个点联通的最短路的长度,而后累加’
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 300010 #define maxn 0x7fffffff using namespace std; bool vis[N]; long long ans; int n,m,x,y,z,s,tot,num,sum,top,tim; int fa[N],low[N],dfn[N],cost[N],stack[N],head[N],belong[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int to,dis,next; }edge[N]; struct Edde { int to,dis,next; }edde[N]; int add(int x,int y,int z) { tot++; edge[tot].to=y; edge[tot].dis=z; edge[tot].next=head[x]; head[x]=tot; } void begin() { s=0,tim=0,sum=0,tot=0; memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(vis,0,sizeof(vis)); memset(head,0,sizeof(head)); memset(belong,0,sizeof(belong)); } void tarjan(int now) { dfn[now]=low[now]=++tim; stack[++top]=now; vis[now]=true; for(int i=head[now];i;i=edge[i].next) { int t=edge[i].to; if(vis[t]) low[now]=min(low[now],dfn[t]); else if(!dfn[t]) tarjan(t),low[now]=min(low[now],low[t]); } if(low[now]==dfn[now]) { sum++;belong[now]=sum; for(;stack[top]!=now;top--) { int x=stack[top]; belong[x]=sum;vis[x]=false; } vis[now]=false;top--; } } void work() { ans=0; for(int i=1;i<=n;i++) for(int j=head[i];j;j=edge[j].next) { int t=edge[j].to; if(belong[i]!=belong[t]) cost[belong[t]]=min(cost[belong[t]],edge[j].dis); } for(int i=1;i<=sum;i++) if(cost[i]!=maxn) ans+=(long long)cost[i]; printf("%lld\n",ans); } int main() { while(scanf("%d%d",&n,&m)!=EOF) { begin(); for(int i=1;i<=n;i++) cost[i]=maxn; for(int i=1;i<=m;i++) x=read(),y=read(),z=read(),add(x+1,y+1,z); for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); work(); } return 0; }
题解:最少的道路条数即为最小生成树须要加的边的条数即为n-1。第二问为求最小生成树中的最长边
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 50010 using namespace std; int n,m,x,y,z,fx,fy,sum,fa[N],ans1,ans2; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int x,y,z; }edge[N<<1]; int cmp(Edge a,Edge b) {return a.z<b.z;} int find(int x) { if(x==fa[x]) return x; fa[x]=find(fa[x]); return fa[x]; } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { edge[i].x=read(); edge[i].y=read(); edge[i].z=read(); } sort(edge+1,edge+1+m,cmp); for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++) { x=edge[i].x,y=edge[i].y; fx=find(x),fy=find(y); if(fx==fy) continue; fa[fx]=fy;sum++; ans2=max(ans2,edge[i].z); if(sum==n-1) break; }ans1=n-1; printf("%d %d",ans1,ans2); return 0; }
Floyd预处理从s到t最短路的条数,当两个点之间有边相连的时候咱们它的最短路得条数初始化为1,而后咱们跑Floyd,当从s到t的最短路须要由k来更新的时候,那么从s到t的最短路的条数即为从s到k最短路的条数*从k到t最短路的·条数,dis[s][k]+dis[k][t]=dis[s][t],那么最短路的条数就要在加上从s到k最短路的条数*从k到t最短路的·条数
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 210 #define maxn 999999 using namespace std; double ans[N],f[N][N]; int n,m,x,y,z,dis[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } int main() { n=read(),m=read(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=(i!=j)*maxn; for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); f[x][y]=f[y][x]=1; dis[x][y]=dis[y][x]=z; } for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(dis[i][j]>dis[i][k]+dis[k][j]) { f[i][j]=f[i][k]*f[k][j]; dis[i][j]=dis[i][k]+dis[k][j]; } else if(dis[i][j]==dis[i][k]+dis[k][j]) f[i][j]+=f[i][k]*f[k][j]; for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i!=k&&k!=j&&dis[i][j]==dis[i][k]+dis[k][j]&&f[i][j]) ans[k]+=(double)f[i][k]*f[k][j]/f[i][j]; for(int i=1;i<=n;i++) printf("%.3lf\n",ans[i]); return 0; }
题意:点有点权,边有边权,从一个点到另外一个点的费用,是通过的全部道路的过路费之和,加上通过的全部的城市(包括起点和终点)的过路费的最大值,给定k个询问,求从s到t的最大值,如不连通输出-1
题解:Floyd,将点权进行排序,而后跑Floyd,咱们在依次跑Floyd的时候保证k=n是的k的点权最大,也就是说这里咱们在更新i到j这条路径的时候依次是使的中间节点的点权最大,这样保证中间节点的点权最大,这样就不用找每条路径上的最大点权了
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #define N 300 #define maxn 9999999 using namespace std; int n,m,x,y,z,p,dy[N],dis[N][N],f[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Node { int c,num; }node[N]; int cmp(Node a,Node b) {return a.c<b.c;} int main() { n=read(),m=read(),p=read(); for(int i=1;i<=n;i++) node[i].c=read(),node[i].num=i; sort(node+1,node+1+n,cmp); for(int i=1;i<=n;i++) dy[node[i].num]=i; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) f[i][j]=dis[i][j]=maxn; for(int i=1;i<=n;i++) f[i][i]=0; for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); x=dy[x],y=dy[y]; f[x][y]=f[y][x]=min(f[x][y],z); } for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { f[i][j]=min(f[i][j],f[i][k]+f[k][j]); dis[i][j]=min(dis[i][j],f[i][j]+max(max(node[i].c,node[k].c),node[j].c)); } while(p--) { x=read(),y=read(); x=dy[x],y=dy[y]; if(dis[x][y]>=maxn) printf("-1\n"); else printf("%d\n",dis[x][y]); } return 0; }
题解:从1点到每一个点的最短路很好求,那么从每一个点到1点的最短路的长度呢?咱们建反向边,那么之前能够反向到达1点的点咱们均可以从1点到达,那么咱们在跑一边spfa,求出从1点到其余点的最短路即为从其余点到1点的最短路的长度。
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 300010 #define maxn 999999 using namespace std; queue<int>q; bool vis[N]; long long ans; int n,m,x,y,z,tot,tot1; int dis[N],dis1[N],head[N],head1[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,dis,next; }edge[N],edge1[N]; int add(int x,int y,int z) { tot++; edge[tot].to=y; edge[tot].dis=z; edge[tot].next=head[x]; head[x]=tot; } int add1(int x,int y,int z) { tot1++; edge1[tot1].to=y; edge1[tot1].dis=z; edge1[tot1].next=head1[x]; head1[x]=tot1; } int spfa(int s) { for(int i=1;i<=n;i++) dis[i]=maxn,vis[i]=false; q.push(s),vis[s]=true,dis[s]=0; while(!q.empty()) { x=q.front();q.pop(),vis[x]=false; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(dis[t]>dis[x]+edge[i].dis) { dis[t]=dis[x]+edge[i].dis; if(vis[t]) continue; q.push(t),vis[t]=false; } } } } int spfa1(int s) { for(int i=1;i<=n;i++) dis1[i]=maxn,vis[i]=false; q.push(s),vis[s]=true,dis1[s]=0; while(!q.empty()) { x=q.front();q.pop(),vis[x]=false; for(int i=head1[x];i;i=edge1[i].next) { int t=edge1[i].to; if(dis1[t]>dis1[x]+edge1[i].dis) { dis1[t]=dis1[x]+edge1[i].dis; if(vis[t]) continue; q.push(t),vis[t]=false; } } } } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); add(x,y,z),add1(y,x,z); } spfa(1),spfa1(1); for(int i=2;i<=n;i++) ans+=(long long)dis[i]+dis1[i]; printf("%lld",ans); return 0; }
题解:统计最短路径条数的时候,若是当前点t是由x更新的,那么到t的最短路条数=到达x点的最短路条数;若是dis[t]=dis[x]+edge[i].dis;最短路径+到达x点的最短路的条数
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 3010 #define maxn 999999 using namespace std; queue<int>q; bool vis[N]; int n,m,x,y,z,tot,sum[N],dis[N],f[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } int spfa(int s) { for(int i=1;i<=n;i++) dis[i]=maxn,vis[i]=false,sum[i]=1; dis[s]=0,vis[s]=true,q.push(s); while(!q.empty()) { x=q.front(),q.pop();vis[x]=false; for(int t=1;t<=n;t++) if(f[x][t]) if(dis[t]>dis[x]+f[x][t]) { dis[t]=dis[x]+f[x][t]; sum[t]=sum[x]; if(vis[t]) continue; q.push(t),vis[t]=true; } else if(dis[t]==dis[x]+f[x][t]) sum[t]+=sum[x]; } } int main() { n=read(),m=read(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) f[i][j]=maxn; for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); f[x][y]=min(f[x][y],z); } spfa(1); if(dis[n]==maxn) printf("No answer"); else printf("%d %d",dis[n],sum[n]); return 0; }
题意:求从任意一个点开始能够挖到的地雷的总数最多,求地雷总数及挖的路径
题解:看数据范围n<=20因此咱们能够跑n边spfa,枚举从每个点开始所能挖到的最多的地雷的数目,若是当前路径的结尾挖到的地雷数最多,则说明这条道路最优,若是咱们美剧到的路径更优,咱们更新答案,更新路径
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 300 using namespace std; queue<int>q; bool vis[N]; int n,m,x,y,e,tot,ans,maxn,sum; int a[N],fa[N],pre[N],dis[N],head[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,dis,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int spfa(int s) { for(int i=1;i<=n;i++) dis[i]=0,vis[i]=false; q.push(s),vis[s]=true,dis[s]=a[s]; while(!q.empty()) { x=q.front();q.pop(),vis[x]=false; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(dis[t]<=dis[x]+a[t]) { dis[t]=dis[x]+a[t]; fa[t]=x; if(vis[t]) continue; q.push(t),vis[t]=true; } } } } int main() { n=read(); for(int i=1;i<=n;i++) a[i]=read(); for(x=1;x<n;x++) { for(int y=x+1;y<=n;y++) { e=read(); if(e) add(x,y); } } for(int s=1;s<=n;s++) { maxn=ans; memset(fa,-1,sizeof(fa)); spfa(s); for(int i=1;i<=n;i++) if(ans<dis[i]) e=i,ans=dis[i]; if(ans==maxn) continue; sum=0; for(;e!=-1;e=fa[e]) pre[++sum]=e; } for(int i=sum;i>=1;i--) printf("%d ",pre[i]); printf("\n%d",ans); return 0; }
题意:在图中找一条从起点到终点的路径,该路径知足路径上的全部点的出边所指向的点都直接或间接与终点连通且路径最短。
题解:题目中要求咱们找到的路径上的点所连得点都必需要与结点直接或间接相连。所以咱们建反向边,而后bfs,搜索到终点不能到达的点,咱们所以能够知道,这些点所链接的点都不能在咱们所要找的道路中出现,而后咱们在跑一边是spfa求一下最短路就行了
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 200100 using namespace std; queue<int>q; bool vis[N],vist[N]; int n,m,s,e,x,y,tot,dis[N],head[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int to,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int spfa(int s) { memset(vis,0,sizeof(vis)); memset(dis,0x3f3f3f3f,sizeof(dis)); dis[s]=0,vis[s]=true;q.push(s); while(!q.empty()) { int x=q.front(); q.pop(); vis[x]=false; for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(dis[to]<=dis[x]+1||vist[to]) continue; dis[to]=dis[x]+1; if(vis[to]) continue; q.push(to); vis[to]=true; } } } int bfs(int s) { vis[s]=true;q.push(s); while(!q.empty()) { int x=q.front(); q.pop(); for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(!vis[to]) q.push(to),vis[to]=true; } } } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) x=read(),y=read(),add(y,x); s=read(),e=read(); bfs(e); for(int i=1;i<=n;i++) if(vis[i]==false) for(int j=head[i];j;j=edge[j].next) vist[edge[j].to]=true; for(int i=1;i<=n;i++) if(!vis[i]) vist[i]=true; spfa(e); if(dis[s]>=0x3f3f3f3f) printf("-1"); else printf("%d",dis[s]); return 0; }
题意:在 M 条路的某一条上安放一叠稻草堆,使这条路的长度加倍,选择一条路干扰使得FJ 从家到牛棚的路长增长最多,求最大增量
题解:咱们知道更改最短路上的边才会使路径长度发生变化,所以咱们先跑一遍最短路,而后记录最短路上的每一条路径,而后暴力枚举每一条路径将其路径长度增为两倍,而后在跑最短路,跑出的最短路与第一次的最短路相减即为答案
小总结:一、对于这种改变一条边,使s到t的路径增量最大,先跑最短路,记录路径,只有更改路径上的点才会使s到t的最短路长度发生改变,所以暴力枚举最短路上的每一条边,看那条边改变后形成的影响最大
二、改变图中一条边的信息,而后求最短路之类的问题,通常是建反向边,跑两遍spfa,而后最短路长度为从1点到这条路径s节点的路径+这条边的长度+终点到这条边的t节点的长度
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 5100 using namespace std; queue<int>q; bool vis[N]; int n,m,x,y,z,tot=1,ans1,ans2,sum; int fa[N],dis[N],head[N],node[N],num[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,dis,next,from; }edge[N<<1]; int add(int x,int y,int z) { tot++; edge[tot].to=y; edge[tot].dis=z; edge[tot].next=head[x]; head[x]=tot; } int spfa(int s) { memset(vis,0,sizeof(vis)); memset(dis,0x3f3f3f3f,sizeof(dis)); vis[s]=true,q.push(s),dis[s]=0; while(!q.empty()) { int x=q.front();q.pop();vis[x]=false; for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(dis[to]>dis[x]+edge[i].dis) { dis[to]=dis[x]+edge[i].dis; fa[to]=x;num[to]=i; if(vis[to]) continue; q.push(to),vis[to]=true; } } } } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); add(x,y,z),add(y,x,z); } memset(fa,-1,sizeof(fa)); spfa(1),ans1=dis[n]; for(int i=n;i!=-1;i=fa[i]) node[++sum]=num[i]; for(int i=1;i<=sum;i++) { edge[node[i]].dis*=2; edge[node[i]^1].dis*=2; spfa(1); ans2=max(ans2,dis[n]); edge[node[i]].dis/=2; edge[node[i]^1].dis/=2; } printf("%d",ans2-ans1); return 0; }
题意:给出每一个村庄重建道路完成的时间,第i个村庄重建完成的时间t[i],能够认为是同时开始重建并在第t[i]天重建完成,而且在当天便可通车。若t[i]为0则说明地震未对此地区形成损坏,一开始就能够通车。以后有Q个询问(x, y, t),对于每一个询问你要回答在第t天,从村庄x到村庄y的最短路径长度为多少
题解:求最短路径,无疑就是最短路的问题了,而后再看数据范围,看着数据范围就应该想到这个题要用Floyd。而后本题的t的单调递增的性质为本题下降了很大的难度,这样咱们就不须要判断而后在处理了,在更新最短路的时候咱们也不用k for循环到最后更新最短路了,由于他说t是单调的,咱们直接一个while循环,从上一次k不能更新的位置直接开始,为何?由于在这以前咱们能跟新的已经跟新完了,若是在更新一遍就至关于作了无用功
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 210 #define maxn 0x3f3f3f3f using namespace std; int n,m,x,y,z,k,T,Q,t[N],dis[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } int main() { n=read(),m=read();k=1; for(int i=1;i<=n;i++) t[i]=read(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=maxn; for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); dis[++x][++y]=dis[y][x]=z; } Q=read(); while(Q--) { x=read(),y=read(),T=read(); x++,y++; while(t[k]<=T&&k<=n) { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); k++; } if(dis[x][y]>=maxn||t[x]>T||t[y]>T) printf("-1\n"); else printf("%d\n",dis[x][y]); } return 0; }
求一个点到另外一个点的最短路径,当这个图中出现负环或两点间没有路径即为不存在从s点到t点的道路,如何判断是否存在负环?跑spfa,若是一个点入队次数超过n次则说明出现负环
spfa判负环dfs版(dfs要比bfs的spfa判负环跑的快)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 200010 using namespace std; bool vis[N],vist; int n,m,t,x,y,z,tot,head[N],dis[N]; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,dis,next; }edge[N<<1]; int add(int x,int y,int z) { tot++; edge[tot].to=y; edge[tot].dis=z; edge[tot].next=head[x]; head[x]=tot; } int spfa(int x) { vis[x]=true; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(dis[t]>dis[x]+edge[i].dis) { dis[t]=dis[x]+edge[i].dis; if(vis[t]||vist) { vist=true; break; } spfa(t); } } vis[x]=false; } void begin() { tot=vist=0; memset(dis,0,sizeof(dis)); memset(head,0,sizeof(head)); memset(vis,false,sizeof(vis)); } int main() { t=read(); while(t--) { begin(); n=read(),m=read(); for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); add(x,y,z); if(z>=0) add(y,x,z); } for(int i=1;i<=n;i++) { spfa(i); if(vist) break; } if(vist) printf("YE5\n"); else printf("N0\n"); } return 0; }
为从星系1 到星系N 的最小代价的路线的代价. 若是这样的路线不存在,输出'No such path'. spfa判负环裸题,bfs版 线路不存在即为出现了负环
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 200010 #define maxn 9999999 using namespace std; queue<int>q; bool vis[N],vist; int n,m,w,x,y,z,tot,head[N],dis[N],in[N]; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,dis,next; }edge[N<<1]; int add(int x,int y,int z) { tot++; edge[tot].to=y; edge[tot].dis=z; edge[tot].next=head[x]; head[x]=tot; } void begin() { tot=vist=0; memset(in,0,sizeof(in)); memset(dis,0,sizeof(dis)); memset(vis,0,sizeof(vis)); memset(head,0,sizeof(head)); } int spfa(int s) { while(!q.empty()) q.pop(); for(int i=1;i<=n;i++) dis[i]=maxn,vis[i]=false; vis[s]=true,dis[s]=0,q.push(s); while(!q.empty()) { x=q.front(),q.pop();vis[x]=false; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(dis[t]>dis[x]+edge[i].dis) { dis[t]=dis[x]+edge[i].dis; if(!vis[t]) in[t]++,vis[t]=true,q.push(t); if(in[t]>n) return true; } } } return false; } int main() { while(1) { begin(); n=read(),m=read(); if(n==0&&m==0) break; for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(),w=read(); add(x,y,z),add(y,x,w); } vist=spfa(1); if(vist||dis[n]==maxn) printf("No such path\n"); else printf("%d\n",dis[n]); } return 0; }
题解:奶牛们从起点出发而后在回到起点,也就是说奶牛走过的路径为一个环,在奶牛走的这个环中ans=全部的乐趣数/路上消耗的全部的时间。
咱们将上面的式子进行变形,能够获得路上消耗的全部时间*ans=全部的乐趣数。——>路上消耗的全部时间*ans-全部的乐趣数=0;
而后咱们在进行二分答案,处理出ans,而后对于每个ans跑n个spfa,判断是否存在负环,若是存在负环就说明当前方案不是最佳答案(存在负环条件:当前点入队次数大于n,固然这种状况是当咱们用bfs的spfa时),让咱们用dfs的spfa时,咱们用一个bool变量表示这个数有没有被访问过,若是被访问过,说明他出现了负环直接结束循环。咱们的ans还能有更大的解,即当路上消耗的全部的时间*ans-全部的乐趣数<0时咱们的ans不是当前最优解继续更新最优解
注意:二分结束的条件为r-l>0.000001,不然会爆long long
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 51000 using namespace std; bool vis[N]; int n,m,x,y,z,tot; int c[N],num[N],head[N]; double ans,mid,l,r,w[N],dis[N]; struct Edge { int to,dis,from,next; }edge[N]; int add(int x,int y,int z) { tot++; edge[tot].to=y; edge[tot].dis=z; edge[tot].next=head[x]; head[x]=tot; } int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int spfa(int x) { vis[x]=true; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(dis[t]>dis[x]+w[i]) { dis[t]=dis[x]+w[i]; if(vis[t]||spfa(t)) { vis[x]=false; return true; } } } vis[x]=false; return false; } int pd() { for(int i=1;i<=n;i++) if(spfa(i)) return true; return false; } int main() { n=read(),m=read(); for(int i=1;i<=n;i++) c[i]=read(); for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); add(x,y,z); } l=0,r=100005; while(r-l>0.0000001) { mid=(l+r)/2; for(int i=1;i<=tot;i++) { int t=edge[i].to; w[i]=(double)mid*edge[i].dis-c[t]; } if(pd()) { ans=mid; l=mid; } else r=mid; } printf("%.2lf",ans); return 0; }
通常是用来求给定n组顺序关系(名次,辈分,大小、、、),判断给出的信息是否存在矛盾,求出符合条件的顺序,如须要求出最小的则须要使用大根堆。拓扑排序还能够用来找一个图中的最长链,也能够用来找从一个点出发的最长链(通过一个点的最长链)不过要先预处理一下
给出n个名次关系,求出符合条件的排名顺序,输出字典序最小的答案
拓扑排序模板题,使用优先队列来使答案的字典序最小
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 510 using namespace std; priority_queue<int,vector<int>,greater<int> >q; int n,m,x,y,tot,sum,in[N],ans[N],head[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,dis,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int tpsort() { for(int i=1;i<=n;i++) if(in[i]==0) q.push(i); while(!q.empty()) { x=q.top(),q.pop(),sum++,ans[sum]=x; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; in[t]--; if(in[t]==0) q.push(t); } } } int main() { while(scanf("%d%d",&n,&m)!=EOF) { sum=0,tot=0; memset(in,0,sizeof(in)); memset(ans,0,sizeof(ans)); memset(head,0,sizeof(head)); memset(edge,0,sizeof(edge)); for(int i=1;i<=m;i++) x=read(),y=read(),add(x,y),in[y]++; tpsort(); for(int i=1;i<sum;i++) printf("%d ",ans[i]); printf("%d\n",ans[sum]); } return 0; }
给出n对顺序关系,询问所给出的关系是否合法,即为不互相矛盾
题解:拓扑排序判环,当拓扑排序入队的点的个数少于n时,即为出现了环
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 510 using namespace std; queue<int>q; int n,m,x,y,tot,sum,in[N],head[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } bool tpsort() { for(int i=1;i<=n;i++) if(in[i]==0) q.push(i); while(!q.empty()) { x=q.front(),q.pop(),sum++; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; in[t]--; if(in[t]==0) q.push(t); } } if(sum==n) return true; return false; } int main() { while(1) { n=read(),m=read(); if(n==0&&m==0) break; tot=0,sum=0; memset(in,0,sizeof(in)); memset(head,0,sizeof(head)); memset(edge,0,sizeof(edge)); for(int i=1;i<=m;i++) x=read(),y=read(),add(x+1,y+1),in[y+1]++; if(tpsort()) printf("YES\n"); else printf("NO\n"); } return 0; }
老板要发工资,每一个人都有一个要求,他必须比谁的工资高,基准工资为888,求共最少须要发多少工资
题解:拓扑排序分层考虑,每一层一种工资,怎么判断是那一层?对于基层咱们是知道他在哪一层的,而后咱们每个入队的点的层数必定是连接他的那个在队中的点的层数+1
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 20100 #define money 888 using namespace std; queue<int>q; long long anss; int n,m,x,y,s,in[N],tot,sum,head[N],ans[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int from,to,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } void begin() { s=0,sum=0,tot=0;anss=0; memset(in,0,sizeof(in)); memset(ans,0,sizeof(ans)); memset(head,0,sizeof(head)); memset(edge,0,sizeof(edge)); } int tpsort() { for(int i=1;i<=n;i++) if(in[i]==0) q.push(i),ans[i]=0; while(!q.empty()) { x=q.front();q.pop(),sum++; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; in[t]--; if(in[t]==0) ans[t]=ans[x]+1,q.push(t); } } } int main() { while(scanf("%d%d",&n,&m)!=EOF) { begin(); for(int i=1;i<=m;i++) { x=read(),y=read(); add(y,x),in[x]++; } tpsort(); if(sum!=n) printf("-1\n"); else { for(int i=1;i<=n;i++) anss+=money+ans[i]; printf("%lld\n",anss); } } return 0; }
题意:给你一系列形如A<B的关系,并要求你判断是否可以根据这些关系肯定这个数列的顺序
题解:拓扑排序+模拟
#include<queue> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 110 using namespace std; int a,b,n,m,s,sum,tot,head[N],in[N],inn[N],p[N]; bool v,unpd,vis[N]; queue<int>q; char ch; int read()//在这里我用的读入优化 { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();} return f*x; } struct Edge { int from,to,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; }//和之前的拓扑排序过程同样,只是多加了几种状况 int tp() { unpd=false; v=false; sum=0;//初始数值 for(int i=1;i<=26;i++)//开始找入读为一的点 { inn[i]=in[i];//因为咱们要没输入一组后就对该序列进行判断,因此咱们新设一个数组inn储存in中的各点入度的值,防止咱们下一次在用时,该店的入度值已不是初始值 if(!inn[i]&&vis[i])//该点的入读为0而且咱们输入过该值 { if(!v) v=true;//咱们要判断有几个入读为0的点,因为若是有两个入读为0的点咱们则没法判断他们的关系由于入读为0的点必定是小的,但这两个值得大小咱们又没法判断 else unpd=true;//unpd用来判断没法判段的状况 q.push(i); p[++sum]=i; } } if(q.empty()) return 1;//若是q数组为空,就说明出现了环,则是存在矛盾的状况。 while(!q.empty())//单纯的拓扑排序,但咱们要在里面多加一点东西:和前面判断入读为0的方法同样,若是删除一个点之后出现了两个入度为0的边,这样咱们将没法判断这两个点的大小 { int x=q.front();v=false;q.pop(); for(int i=head[x];i;i=edge[i].next) { inn[edge[i].to]--; if(!inn[edge[i].to]) { q.push(edge[i].to); if(!v) v=true; else unpd=true; p[++sum]=edge[i].to; } } } if(sum!=s) return 1;//说明出现了环。 if(unpd) return 2; return 0; } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { cin>>ch,a=ch-64;if(!vis[a]) vis[a]=true,s++;//s是用来存咱们输入的元素的个数,方便后面判断s值与sum值的关系(来判断是否为环) cin>>ch;//这个在输入时其实咱们也能够用一个数组来表示,在这里咱们因为有一个<是没有用的,因此咱们直接输入就行了 cin>>ch,b=ch-64;if(!vis[b]) vis[b]=true,s++;//vis用来表示该数有值 add(a,b);//在这里咱们将咱们输入的字符转化成了数字,方便后面进行操做 in[b]++;//储存入读 if(tp()==1) //在这里咱们必须让他等于1,由于咱们在tp函数中返回的是0,1,2 { printf("Inconsistency found after %d relations.",i);//存在矛盾 return 0; } if(sum==n&&!tp())//sum=n,说明该序列中全部的数都进行了排序,都能肯定他们的位置 { printf("Sorted sequence determined after %d relations: ",i); for(int j=1;j<=n;j++) printf("%c",p[j]+64);//在最开始的时候我居然让他输出p[i]+64.这告诉咱们要注意咱们循环使用的变量i,j printf("."); return 0; } } printf("Sorted sequence cannot be determined.");//因为咱们在tp函数中只有三种状况,除了前两种剩下的就是这一种了。 return 0; }
法一:建反向边,双向dfs找从1点可以达到的最长链,可以到达1点的最长链,而后枚举须要反转的边,若能更新最大值,则更新最大值。
#include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 210000 using namespace std; queue<int>q; int n,m,x,y,s,tot,tot1,top,tim; bool vis[N],vis1[N],vis2[N],vist1[N],vist2[N]; int xx[N],yy[N],in1[N],in2[N],head1[N],head2[N],dfn[N]; int low[N],sum[N],ans1[N],ans2[N],head[N],stack[N],belong[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,next; }edge[N],edge1[N],edge2[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int add1(int x,int y) { tot1++; edge1[tot1].to=y; edge1[tot1].next=head1[x]; edge2[tot1].to=x; edge2[tot1].next=head2[y]; head1[x]=head2[y]=tot1; } int tarjan(int x) { dfn[x]=low[x]=++tim; stack[++top]=x,vis[x]=true; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(vis[t]) low[x]=min(low[x],dfn[t]); else if(!dfn[t]) tarjan(t),low[x]=min(low[t],low[x]); } if(low[x]==dfn[x]) { s++,sum[s]++,belong[x]=s; for(;stack[top]!=x;top--) { sum[s]++; vis[stack[top]]=false; belong[stack[top]]=s; } top--,vis[x]=false; } } int shink_point() { for(int i=1;i<=n;i++) for(int j=head[i];j;j=edge[j].next) { int t=edge[j].to; if(belong[i]!=belong[t]) add1(belong[i],belong[t]); } } int dfs1(int x) { vis1[x]=true; for(int i=head1[x];i;i=edge1[i].next) { int t=edge1[i].to; if(ans1[t]<ans1[x]+sum[t]) ans1[t]=ans1[x]+sum[t],dfs1(t); } } int dfs2(int x) { vis2[x]=true; for(int i=head2[x];i;i=edge2[i].next) { int t=edge2[i].to; if(ans2[t]<ans2[x]+sum[t]) ans2[t]=ans2[x]+sum[t],dfs2(t); } } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) xx[i]=read(),yy[i]=read(),add(xx[i],yy[i]); for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); shink_point(); ans1[belong[1]]=ans2[belong[1]]=sum[belong[1]]; dfs1(belong[1]);dfs2(belong[1]); int answer=2*sum[belong[1]]; for(int i=1;i<=m;i++) { x=belong[yy[i]],y=belong[xx[i]]; if(vis1[x]&&vis2[y]) answer=max(answer,ans1[x]+ans2[y]); } printf("%d",answer-sum[belong[1]]); return 0; }
法二:拓扑排序求最长链
们若是直接进行拓扑排序的话,咱们会意识到一个问题:缩完点之后直接统计出来入度为零的点并不是是咱们所须要的点1,咱们要跑最长链的话咱们须要从1点开始跑,也就是说咱们的起点必须是1,怎样作到这一点??咱们要作到起点是一的话咱们必须让1的入度为零,从一点开始更新与他相连的点。重新统计他们的入读,也就是说咱们将这个可能出现环的图抽离成一颗树,这棵树的树根为1点。而后再进行拓扑排序,找出最长链。
#include<queue> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 210000 using namespace std; int n,m,x,y,s,tot,tat,top,tim; bool vis[N],vis1[N],vis2[N]; int xx[N],yy[N],in1[N],in2[N],head1[N],head2[N],dfn[N]; int low[N],sum[N],ans1[N],ans2[N],head[N],stack[N],belong[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int to,from,next; }edge[N],edge1[N],edge2[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int add1(int x,int y) { tat++; edge1[tat].to=y; edge1[tat].next=head1[x]; edge2[tat].to=x; edge2[tat].next=head2[y]; head1[x]=head2[y]=tat; } int tarjan(int now) { dfn[now]=low[now]=++tim; vis[now]=true;stack[++top]=now; for(int i=head[now];i;i=edge[i].next) { int t=edge[i].to; if(vis[t]) low[now]=min(dfn[t],low[now]); else if(!dfn[t]) tarjan(t),low[now]=min(low[t],low[now]); } if(low[now]==dfn[now]) { s++,belong[now]=s,sum[s]++; for(;stack[top]!=now;top--) belong[stack[top]]=s,sum[s]++,vis[stack[top]]=false; vis[now]=false;top--; } } int shink_point() { for(int i=1;i<=m;i++) for(int j=head[i];j;j=edge[j].next) if(belong[i]!=belong[edge[j].to]) add1(belong[i],belong[edge[j].to]); } int dfs1(int s) { for(int i=head1[s];i;i=edge1[i].next) { int t=edge1[i].to; if(!in1[t]) dfs1(t); in1[t]++; } } int dfs2(int s) { for(int i=head2[s];i;i=edge2[i].next) { int t=edge2[i].to; if(!in2[t]) dfs2(t); in2[t]++; } } int tpsort(int *in,Edge *edge,int *head,bool *vis,int *ans) { queue<int>q; q.push(belong[1]); while(!q.empty()) { int x=q.front();q.pop();vis[x]=true; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; in[t]--; if(!in[t]) q.push(t); ans[t]=max(ans[t],ans[x]+sum[t]); } } } int main() { n=read(),m=read(); int answer=0; for(int i=1;i<=m;i++) xx[i]=read(),yy[i]=read(),add(xx[i],yy[i]); for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); shink_point(); dfs1(belong[1]),dfs2(belong[1]); ans1[belong[1]]=ans2[belong[1]]=sum[belong[1]]; tpsort(in1,edge1,head1,vis1,ans1); tpsort(in2,edge2,head2,vis2,ans2); answer=2*sum[belong[1]]; for(int i=1;i<=m;i++) { x=belong[yy[i]],y=belong[xx[i]]; if(vis1[x]&&vis2[y]) answer=max(answer,ans1[x]+ans2[y]); } printf("%d",answer-sum[belong[1]]); return 0; }
最小顶点覆盖是指最少的顶点数使得二分图中的每条边都至少与其中一个点相关联,二分图的最小顶点覆盖数=二分图的最大匹配数;
最小路径/点覆盖是指用尽可能少的不相交路径覆盖二分图中的全部顶点。二分图的最小路径覆盖数=|V|-二分图的最大匹配日数
最大独立集(指寻找一个点集,使得其中任意两点在图中无对应边)=|V|-二分图的最大匹配数
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 1010 using namespace std; bool vis[N]; int n,m,e,x,y,ans,girl[N],map[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } int find(int x) { for(int i=1;i<=m;i++) if(map[x][i]&&!vis[i]) { vis[i]=true; if(find(girl[i])||!girl[i]) { girl[i]=x; return true; } } return false; } int main() { n=read(),m=read(),e=read(); for(int i=1;i<=e;i++) x=read(),y=read(),map[x][y]=1; for(int i=1;i<=n;i++) { memset(vis,0,sizeof(vis)); if(find(i)) ans++; } printf("%d",ans); return 0; }
题解:行列匹配,对于这种每次只能消灭一行或者一列的操做,咱们能够将每一行当作一个集合,将每一列当作一个集合,而后跑二分图匹配,题目中要求最少多少步能够将小星星所有消灭,就是让求最少用多少条边将全部的点所有覆盖,也就是最小点覆盖问题=最大匹配数,而后就是个模板题了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 1010 using namespace std; bool vis[N]; int n,m,e,x,y,ans,girl[N],map[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } int find(int x) { for(int i=1;i<=n;i++) if(map[x][i]&&!vis[i]) { vis[i]=true; if(find(girl[i])||!girl[i]) { girl[i]=x; return 1; } } return 0; } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) x=read(),y=read(),map[x][y]=1; for(int i=1;i<=n;i++) { memset(vis,0,sizeof(vis)); if(find(i)) ans++; } printf("%d",ans); return 0; }
题意:给出一些喜欢关系,一头牛只能选则一个摊位,牛只有在本身喜欢的摊位上才能产奶,问最多产奶总数
小总结:对于喜欢类型(或者一个点能够选择另外一个点)的问题,求最多能知足的人数(对数)通常是二分图求最大匹配裸题
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 1010 using namespace std; bool vis[N]; int n,m,k,x,y,ans,girl[N],map[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } int find(int x) { for(int i=1;i<=m;i++) if(map[x][i]&&!vis[i]) { vis[i]=true; if(find(girl[i])||girl[i]==-1) { girl[i]=x; return 1; } } return 0; } int main() { while(scanf("%d%d",&n,&m)!=EOF) { ans=0; memset(map,0,sizeof(map)); memset(girl,-1,sizeof(girl)); for(int i=1;i<=n;i++) { k=read(); while(k--) x=read(),map[i][x]=1; } for(int i=1;i<=n;i++) { memset(vis,0,sizeof(vis)); if(find(i)) ans++; } printf("%d\n",ans); } return 0; }
题意:给出两台机器A、B,机器A上有n种模式,机器B上有m种模式,现有k个须要运行的任务,每一个任务有对应的运行模式,(i, x, y)表示i任务对应的A B机器上的运行模式为x,y. 开始的工做模式都是0.每台机器上的任务能够按照任意顺序执行,可是每台机器每转换一次模式须要重启一次。求机器重启的最少次数
题解:咱们对于每个任务连边,而后咱们如今要完成任务而且咱们要要求机器重启的次数最少,那么也就是说咱们须要用到最少的点把全部的边连起来,这样就转化成了最小点覆盖的裸题了 最小点覆盖等于最大匹配数
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 1010 using namespace std; bool vis[N]; int n,m,k,x,y,z,ans,girl[N],map[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int find(int x) { for(int i=1;i<=m;i++) { if(!vis[i]&&map[x][i]) { vis[i]=true; if(girl[i]==-1||find(girl[i])) {girl[i]=x; return 1;} } } return 0; } int main() { while(1) { n=read();if(n==0) break; m=read(),k=read();ans=0; memset(map,0,sizeof(map)); memset(girl,-1,sizeof(girl)); for(int i=1;i<=k;i++) { z=read(),x=read(),y=read(); map[x][y]=1; } for(int i=1;i<=n;i++) { memset(vis,0,sizeof(vis)); if(find(i)) ans++; } printf("%d\n",ans); } return 0; }
题意:喜欢猫的必定不喜欢狗,喜欢狗的必定不喜欢猫,咱们要选则养猫仍是养狗,使知足的人最多
题解: 咱们将喜欢猫的和喜欢狗的划分红两个集合,而后将这两个集合中存在矛盾的点连边,构图,跑二分图匹配,答案及为最大独立集,最大独立集=总点数-最大匹配数
小总结:给出n对喜欢关系,喜欢A的不喜欢B,喜欢B的不喜欢A,咱们选择一我的,问最多能是多少我的知足,这个时候咱们将喜欢A的当作一个几何,喜欢B的当作一个集合,而后将这两个集合中存在矛盾的连边,跑最大独立集
题目:一些学生之间是朋友关系(关系不能传递),问可否将学生分红两堆使得同一堆的学生之间不是朋友。若是不能够输出“No”,能够的话输出最多能够分出几对小盆友。
题解:先二分图染色判断其是不是二分图,而后在跑最大匹配
#include<queue> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 510 using namespace std; bool flag,vis[N]; int n,m,x,y,tot,ans,col[N],girl[N],head[N],map[N][N]; queue<int>q; struct Edge { int from,to,next; }edge[N*N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int find(int x) { for(int i=1;i<=n;i++) { if(!vis[i]&&map[x][i]) { vis[i]=true; if(girl[i]==-1||find(girl[i])){girl[i]=x; return 1;} } } return 0; } int color(int s) { queue<int>q; q.push(s); col[s]=0; while(!q.empty()) { int x=q.front(); for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(col[t]!=-1){if(col[t]==col[x]) {flag=true; return 1;}} else { col[t]=col[x]^1; q.push(t); } } q.pop(); } return 0; } int main() { while(scanf("%d%d",&n,&m)!=EOF) { ans=0;flag=false;tot=0; memset(map,0,sizeof(map)); memset(col,-1,sizeof(col)); memset(edge,0,sizeof(edge)); memset(head,0,sizeof(head)); for(int i=1;i<=m;i++) { x=read(),y=read(); map[x][y]=1; add(x,y),add(y,x); } for(int i=1;i<=n;i++) if(col[i]==-1) { if(color(i)) break; } if(flag) {printf("No\n"); continue;} memset(girl,-1,sizeof(girl)); for(int i=1;i<=n;i++) { memset(vis,0,sizeof(vis)); if(find(i)) ans++; } printf("%d\n",ans); } return 0; }
KM算法
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 501000 using namespace std; int n,m,tot,x,y,root; int fa[N],top[N],size[N],deep[N],head[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } struct Edge { int to,next; }edge[N<<1]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int dfs(int x) { size[x]=1; deep[x]=deep[fa[x]]+1; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(fa[x]==t) continue; fa[t]=x,dfs(t); size[x]+=size[t]; } } int dfs1(int x) { int t=0; if(!top[x]) top[x]=x; for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(fa[x]!=to&&size[t]<size[to]) t=to; } if(t) top[t]=top[x],dfs1(t); for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(fa[x]!=to&&to!=t) dfs1(to); } } int LCA(int x,int y) { for(;top[x]!=top[y];x=fa[top[x]]) if(deep[top[x]]<deep[top[y]]) swap(x,y); if(deep[x]>deep[y]) swap(x,y); return x; } int main() { n=read(),m=read(),root=read(); for(int i=1;i<n;i++) x=read(),y=read(),add(x,y),add(y,x); dfs(root),dfs1(root); for(int i=1;i<=m;i++) { x=read(),y=read(); printf("%d\n",LCA(x,y)); } return 0; }