能夠結合這篇博客進行復習: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; }