//主要是根据各类网上资料作笔记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; }