//主要是根據各類網上資料作筆記php
\(f[i][j]\):從\(i\)號頂點到\(j\)號頂點只通過前\(k\)號點的最短路程html
for (k=1;k<=n;k++) for (i=1;i<=n;i++) for (j=1;j<=n;j++) f[i][j] = min(f[i][j],f[i][k]+f[k][j]);
\(k\)是階段 因此必須位於最外層 而\(i和j\)爲附加狀態node
給一個正權無向圖,找一個最小權值和的環c++
這必定是一個簡單環 考慮環上編號最大的結點\(u\)git
f[u-1][x][y]
和\((u,x), (u,y)\)共同構成了環。算法
在 Floyd 的過程當中枚舉\(u\),計算這個和的最小值便可數組
有向圖的最小環問題 可枚舉起點\(s=1\sim n\) 執行對優化的\(Dijsktra\)求解單源最短路徑\(s\)必定爲第一個被從堆中取出節點 掃描\(s\)全部出邊 擴展、更新完成後 令\(d[s]=+\infty\) 而後繼續求解 當s第二次被從堆中取出時 \(d[s]\)就是通過點\(s\)的最小環長度閉包
找最小環 並輸出一個最小環方案app
#include<bits/stdc++.h> using namespace std; const int N=100+10,M=1e5+50,inf=0x3f3f3f3f; int n,m,mp[N][N],dis[N][N],pos[N][N]; vector<int>path; void get_path(int x,int y){ if(!pos[x][y]) return; get_path(x,pos[x][y]); path.push_back(pos[x][y]); get_path(pos[x][y],y); } int main(){ // freopen("in.txt","r",stdin); scanf("%d%d",&n,&m); int ans=inf; memset(mp,inf,sizeof(mp)); for(int i=1;i<=n;++i) mp[i][i]=0; for(int i=1,x,y,w;i<=m;++i) scanf("%d%d%d",&x,&y,&w),mp[x][y]=mp[y][x]=w; memcpy(dis,mp,sizeof(mp)); for(int k=1;k<=n;++k){ for(int i=1;i<k;++i) for(int j=i+1;j<k;++j) if((long long)dis[i][j]+mp[i][k]+mp[k][j]<ans){ ans=dis[i][j]+mp[i][k]+mp[k][j]; path.clear(),path.push_back(i); get_path(i,j);path.push_back(j),path.push_back(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]) dis[i][j]=dis[i][k]+dis[k][j],pos[i][j]=k; } if(ans==inf) return puts("No solution."),0; for(int i=0;i<path.size();++i) printf("%d ",path[i]); return 0; }
傳遞閉包優化
已知一個有向圖中任意兩點之間是否有連邊,要求判斷任意兩點是否連通
按照 Floyd 的過程,逐個加入點判斷一下。
只是此時的邊的邊權變爲 \(1/0\),而取 \(min\)變成了與運算。
再進一步用 bitset 優化,複雜度能夠到 \(O\left(\dfrac{n^3}w\right)\)
for (k=1;k<=n;k++) for (i=1;i<=n;i++) for (j=1;j<=n;j++) f[i][j]|=f[i][k]&f[k][j];
// std::bitset<SIZE> f[SIZE]; for (k = 1; k <= n; k++) for (i = 1; i <= n; i++) if (f[i][k]) f[i] = f[i] & f[k];
給定 n 個變量,m 個不等式。
不等式之間具備傳遞性,即若 A>B 且 B>C ,則 A>C。
判斷這 m 個不等式是否有矛盾。
若存在矛盾,則求出\(T\)的最小值,知足僅用前\(T\)個不等式就能肯定不等式之間存在矛盾。
若無矛盾,則判斷這 m 個不等式是否能肯定每一對變量之間的關係。
若能,則求出\(T\)的最小值,知足僅用前\(T\)個不等式就能肯定每一對變量之間的大小關係。
==不得不說 lyd勞斯的代碼真的很好 精簡高效
\(mp[i][j]\)表示\(i<j\)
#include<bits/stdc++.h> using namespace std; const int N=100+10,M=1e5+50,inf=0x3f3f3f3f; int n,m,x,y,mp[N][N],ok1,ok2; char opt[10]; int main(){ freopen("in.txt","r",stdin); //freopen("and.out","w",stdout); while(~scanf("%d%d",&n,&m)&&(n+m)){ ok1=ok2=0; memset(mp,0,sizeof(mp)); int i; for(i=1;i<=n;++i) mp[i][i]=1; for(i=1;i<=m;++i){ scanf("%s",opt),ok2=0; x=opt[0]-'A'+1,y=opt[2]-'A'+1,mp[x][y]=1; for(int j=1;j<=n;++j) for(int k=1;k<=n;++k) mp[j][k]|=mp[j][x]&mp[y][k]; for(int j=1;j<n;++j) for(int k=j+1;k<=n;++k) if(mp[j][k]&mp[k][j]){ printf("Inconsistency found after %d relations.\n",i); j=n,ok1=1;break; } else if(!(mp[j][k]|mp[k][j])) ok2=1; if(ok1) break; if(!ok2){ printf("Sorted sequence determined after %d relations: ",i); int a[N]; memset(a,0,sizeof(a)) ; for(int j=1;j<=n;++j) for(int k=j+1;k<=n;++k) if(mp[j][k]) ++a[k];else ++a[j]; for(int j=0;j<n;++j) for(int k=1;k<=n;++k) if(a[k]==j) {putchar(k+'A'-1);break;} puts(".");break; } } if(i>m) puts("Sorted sequence cannot be determined."); for(++i;i<=m;++i) scanf("%s",opt); } return 0; }
給定一張由T條邊構成的無向圖,點的編號爲1~1000之間的整數。
求從起點S到終點E剛好通過N條邊(能夠重複通過)的最短路。
矩陣乘法和floyd的組合!
#include<bits/stdc++.h> using namespace std; const int N=200+10,M=1e6+10,inf=0x3f3f3f3f; int n,m,s,t,vc[M]; struct mar{ int a[N][N]; mar operator *(mar &X){ mar c; memset(c.a,inf,sizeof(c.a)); for(int i=1;i<=vc[0];++i) for(int j=1;j<=vc[0];++j) for(int k=1;k<=vc[0];++k) c.a[i][j]=min(c.a[i][j],a[i][k]+X.a[k][j]); return c; } }ans,mp; void qpow(mar a,int b){ ans=a,--b; while(b){ if(b&1) ans=ans*a; a=a*a,b>>=1; } } int main(){ // freopen("in.txt","r",stdin); scanf("%d%d%d%d",&n,&m,&s,&t); memset(mp.a,inf,sizeof(mp.a)); for(int i=1,x,y,w;i<=m;++i){ scanf("%d%d%d",&w,&x,&y); x=(!vc[x]?(vc[x]=++vc[0]):vc[x]), y=(!vc[y]?(vc[y]=++vc[0]):vc[y]); mp.a[x][y]=mp.a[y][x]=min(w,mp.a[x][y]); } //for(int i=1;i<=vc[0];++i) mp.a[i][i]=0; qpow(mp,n); printf("%d",ans.a[vc[s]][vc[t]]); return 0; }
用floyd跑差分約束==
由於砝碼大小隻有一、二、3 因此未知時最大差值爲2 最小差值爲-2
由\(A+B>C+D\)能夠轉爲\(A-C>D-B\) 而後就挨個判斷就行了
注意判斷等於時的條件
#include<bits/stdc++.h> using namespace std; #define Max(x,y) ((x)>(y)?(x):(y)) #define Min(x,y) ((x)<(y)?(x):(y)) const int N=50+10,M=1e6+10,inf=0x3f3f3f3f; int n,A,B,c1,c2,c3,mx[N][N],mn[N][N]; char opt[N]; int main(){ freopen("in.txt","r",stdin); scanf("%d%d%d",&n,&A,&B); for(int i=1;i<=n;++i){ scanf("%s",opt+1);mx[i][i]=mn[i][i]=0; for(int j=1;j<=n;++j) if(j!=i){ if(opt[j]=='-') mn[i][j]=-2,mx[i][j]=-1; else if(opt[j]=='+') mn[i][j]=1,mx[i][j]=2; else if(opt[j]=='=') mx[i][j]=mn[i][j]=0; else mn[i][j]=-2,mx[i][j]=2; } } for(int k=1;k<=n;++k) for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) mn[i][j]=Max(mn[i][j],mn[i][k]+mn[k][j]), mx[i][j]=Min(mx[i][j],mx[i][k]+mx[k][j]); for(int i=1;i<=n;++i) if(i!=A&&i!=B) for(int j=i+1;j<=n;++j) if(j!=A&&j!=B){ if(mn[A][i]>mx[j][B]||mn[B][i]>mx[j][A]) ++c1; if(mn[i][A]>mx[B][j]||mn[i][B]>mx[A][j]) ++c3; if(mn[A][i]==mx[A][i]&&mn[j][B]==mx[j][B]&&mn[A][i]==mn[j][B])++c2; else if(mn[B][i]==mx[B][i]&&mn[j][A]==mx[j][A]&&mn[B][i]==mn[j][A])++c2; } printf("%d %d %d",c1,c2,c3); return 0; }
==就不說了
SPFA 的時間複雜度爲\(O(kM)(k\approx2)\)玄學),但理論上界爲\(O(NM)\)
SPFA 的優化之SLF即 Small Label First。
即在新元素加入隊列時,若是隊首元素權值大於新元素權值,那麼就把新元素加入隊首,不然依然加入隊尾。
該優化在確實在一些圖上有顯著效果,其複雜度也有保證,可是若是有負權邊的話,能夠直接卡到指數級。
若是有向圖的邊權值全爲正數,那麼有一種複雜度有保證的單源最段路
算法——\(Dijkstra\) 算法 它的複雜度是\(O(|E| log |V|)\)
事實上,\(Dijkstra\) 算法的思想和 \(Prim\) 有不少相似之處 \(Dijkstra\) 算法
維護了一個未訪問的結點集合\(T\)以及一個從\(s\)到結點\(u\)的當前距離 \(dist[u]\)
主要思想是,將結點分紅兩個集合已肯定最短路長度的,未肯定的 一開始第一個集合裏只有\(S\)
時間複雜度:只用分析集合操做, 次 delete-min
, 次 decrease-key
。
若是用暴力:\(O(n^2+m)\) 若是用堆 \(O((n+m)log\ n)\)
若是用 priority_queue:\(O((n+m)log\ m)\) (注:若是使用 priority_queue,沒法刪除某一箇舊的結點,只能插入一個權值更小的編號相同結點,這樣操做致使堆中元素是 \(O(m)\)的)
若是用線段樹(ZKW 線段樹): \((O(n+m)log\ n)\)
若是用 Fibonacci 堆:\(O(nlog\ n+m)\)(這就是爲啥優秀了)。
它的正確性在於,在未訪問集合\(T\)中結點的\(dist\)是從\(s\)開始通過已經
訪問集合中的結點到達它的最短路
若是選出的當前結點\(u\)的\(dist\)不是最終的最小值,那麼它最終的最短
路必定是要通過一個此時\(T\)中的其它結點再到\(u\)。這時那個結點的\(dist\)肯
定要小於\(u\)的\(dist\),這就和\(u\)是\(dist\)最小的結點矛盾了!
開一個pre
數組,在更新距離的時候記錄下來後面的點是如何轉移過去的,算法結束前再遞歸地輸出路徑便可。
好比 Floyd 就要記錄pre[i][j] = k;
,Bellman-Ford 和 Dijkstra 通常記錄 pre[v] = u
。
Floyd | Bellman-Ford | Dijkstra |
---|---|---|
每對結點之間的最短路 | 單源最短路 | 單源最短路 |
無負環的圖 | 任意圖 | 非負權圖 |
\(O(N^3)\) | \(O ( NM )\) | \(O((N+M)log\ M)\) |
通過改造,srwudi的跳樓機能夠採用如下四種方式移動:
向上移動x層;向上移動y層;向上移動z層;回到第一層。
一個月黑風高的大中午,DJL來到了srwudi的家,如今他在srwudi家的第一層,碰巧跳樓機也在第一層。DJL想知道,他能夠乘坐跳樓機前往的樓層數。
yyb:先只考慮只用\(y,z\)兩種移動方式,它們必定可以到達一些樓層,
那麼這些樓層再只用\(x\)拓展就可以計算答案。
那麼咱們這樣子計算答案,設\(dis[i]\)表示能夠到達\(mod\ x=i\)樓層的最小值,
很巧妙!同餘最短路
#include<bits/stdc++.h> using namespace std; #define ll long long const int N=100000+10,M=100000+10,inf=0x3f3f3f3f; int x,y,z; ll n,ans=0; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } int head[N],tot=0; struct edge{int v,w,nxt;}e[M<<2]; void add(int u,int v,int w){ e[++tot]=(edge){v,w,head[u]},head[u]=tot; } queue<int>q;bool vis[N]; ll dis[N]; void spfa(){ memset(dis,inf,sizeof(dis)); memset(vis,0,sizeof(vis)); q.push(1),vis[1]=1,dis[1]=1; while(!q.empty()){ int u=q.front();q.pop(),vis[u]=0; for(int i=head[u],v,w;i;i=e[i].nxt) if(dis[v=e[i].v]>dis[u]+(w=e[i].w)){ dis[v]=dis[u]+w; if(!vis[v]) q.push(v),vis[v]=1; } } } int main(){ // freopen("in.txt","r",stdin); rd(n),rd(x),rd(y),rd(z); if(n==1||x==1||y==1||z==1) return printf("%lld",n),0; for(int i=0;i<x;++i) add(i,(i+y)%x,y),add(i,(i+z)%x,z); spfa(); for(int i=0;i<x;++i) if(dis[i]<=n) ans+=(n-dis[i])/x+1; printf("%lld",ans); return 0; }
和跳樓機那題是同樣的==
#include<bits/stdc++.h> using namespace std; #define Max(x,y) ((x)>(y)?(x):(y)) #define Min(x,y) ((x)<(y)?(x):(y)) #define ll long long const int N=12+10,M=500000+10,inf=0x3f3f3f3f; int n,a[N]; ll b1,b2,ans=0; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } int head[M],tot=0; struct edge{int v,w,nxt;}e[M*20]; void add(int u,int v,int w){ e[++tot]=(edge){v,w,head[u]},head[u]=tot; } queue<int>q;bool vis[M]; ll dis[M]; void spfa(){ memset(dis,inf,sizeof(dis)); memset(vis,0,sizeof(vis)); q.push(0),vis[0]=1,dis[0]=0; while(!q.empty()){ int u=q.front();q.pop(),vis[u]=0; for(int i=head[u],v,w;i;i=e[i].nxt) if(dis[v=e[i].v]>dis[u]+(w=e[i].w)){ dis[v]=dis[u]+w; if(!vis[v]) q.push(v),vis[v]=1; } } } int main(){ freopen("in.txt","r",stdin); rd(n),rd(b1),rd(b2); for(int i=1;i<=n;++i) rd(a[i]); sort(a+1,a+n+1); for(int i=0;i<a[1];++i) for(int j=2;j<=n;++j) add(i,(i+a[j])%a[1],a[j]); spfa(); for(int i=0;i<a[1];++i){ if(dis[i]<=b1-1) ans-=(b1-1-dis[i])/a[1]+1; if(dis[i]<=b2) ans+=(b2-dis[i])/a[1]+1; } printf("%lld",ans); return 0; }
法一:二分+01BFS
很好想der 二分路徑中邊權從小到大排第k條的值 而後BFS時 不超過mid的邊權爲0 超過mid的爲1
法二:分層最短路
#include<bits/stdc++.h> using namespace std; #define Max(x,y) ((x)>(y)?(x):(y)) #define Min(x,y) ((x)<(y)?(x):(y)) const int N=1000+10,M=10000+10,inf=0x3f3f3f3f; int n,m,K; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } int head[N],tot=0; struct edge{int v,w,nxt;}e[M<<1]; void add(int u,int v,int w){ e[++tot]=(edge){v,w,head[u]},head[u]=tot; } int dis[N][N];bool vis[N][N]; struct node{int id,us;}; queue<node>q; void spfa(){ memset(dis,inf,sizeof(dis)); memset(vis,0,sizeof(vis)); q.push((node){1,0}),vis[1][0]=1,dis[1][0]=0; while(!q.empty()){ node nw=q.front();q.pop(); int u=nw.id,us=nw.us;vis[u][us]=0; for(int i=head[u],v,w;i;i=e[i].nxt){ if(dis[v=e[i].v][us]>max(dis[u][us],(w=e[i].w))){//不用 dis[v][us]=max(dis[u][us],(w=e[i].w)); if(!vis[v][us]) q.push((node){v,us}),vis[v][us]=1; } if(us<K&&dis[v][us+1]>dis[u][us]){ dis[v][us+1]=dis[u][us]; if(!vis[v][us+1]) q.push((node){v,us+1}),vis[v][us+1]=1; } } } } int main(){ freopen("in.txt","r",stdin); rd(n),rd(m),rd(K); for(int i=1,u,v,w;i<=m;++i) rd(u),rd(v),rd(w),add(u,v,w),add(v,u,w); spfa(); if(dis[n][K]==inf) return puts("-1"),0; printf("%d",dis[n][K]); return 0; }
重慶城裏有 \(n\)個車站,\(m\)條雙向公路鏈接其中的某些車站。每兩個車站最多用一條公路鏈接,從任何一個車站出發均可以通過一條或者多條公路到達其餘車站,但不一樣的路徑須要花費的時間可能不一樣。在一條路徑上花費的時間等於路徑上全部公路須要的時間之和。
佳佳的家在車站 ,他有五個親戚,分別住在車站\(a,b,c,d,e\)。過年了,他須要從本身的家出發,拜訪每一個親戚(順序任意),給他們送去節日的祝福。怎樣走,才須要最少的時間?
==分別dij求出\(1,a,b,c,d\)點到各點的最短路 而後枚舉順序
我用的\(next\_permutation\)來搞全排列==
#include<bits/stdc++.h> using namespace std; #define Max(x,y) ((x)>(y)?(x):(y)) #define Min(x,y) ((x)<(y)?(x):(y)) #define ll long long typedef pair<int,int>pii; const int N=50000+10,M=1e5+10,inf=0x3f3f3f3f; int n,m,a[10],ans=inf,nw=0; int b[10]={0,1,2,3,4,5}; template <class t>void rd(t &x){ x=0;int w=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); x=w?-x:x; } int head[N],tot=0; struct edge{int v,w,nxt;}e[M<<1]; void add(int u,int v,int w){ e[++tot]=(edge){v,w,head[u]},head[u]=tot; } int dis[7][N];bool vis[N]; priority_queue<pii,vector<pii>,greater<pii> >q; void dij(int id,int s){ memset(vis,0,sizeof(vis)); dis[id][s]=0,q.push(make_pair(0,s)); while(!q.empty()){ int u=q.top().second;q.pop(); if(vis[u]) continue;vis[u]=1; for(int i=head[u],v,w;i;i=e[i].nxt){ if(dis[id][v=e[i].v]>dis[id][u]+(w=e[i].w)){ dis[id][v]=dis[id][u]+w; q.push(make_pair(dis[id][v],v)); } } } } void cal(int id){ nw+=dis[b[id-1]][a[b[id]]]; if(id==5){ans=Min(ans,nw),nw=0;return;} cal(id+1); } int main(){ freopen("in.txt","r",stdin); rd(n),rd(m); for(int i=1;i<=5;++i) rd(a[i]);a[0]=1; for(int i=1,u,v,w;i<=m;++i) rd(u),rd(v),rd(w),add(u,v,w),add(v,u,w); memset(dis,inf,sizeof(dis)); for(int i=0;i<=5;++i) dij(i,a[i]); cal(1); while(next_permutation(b+1,b+6)) cal(1); printf("%d",ans); return 0; }