//主要是根据各种网上资料做笔记
Floyd
(f[i][j]):从(i)号顶点到(j)号顶点只经过前(k)号点的最短路程
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)为附加状态
应用
-
给一个正权无向图,找一个最小权值和的环
这一定是一个简单环 考虑环上编号最大的结点(u)
f[u-1][x][y]
和((u,x), (u,y))共同构成了环。在 Floyd 的过程中枚举(u),计算这个和的最小值即可
有向图的最小环问题 可枚举起点(s=1sim n) 执行对优化的(Dijsktra)求解单源最短路径(s)一定为第一个被从堆中取出节点 扫描(s)所有出边 扩展、更新完成后 令(d[s]=+infty) 然后继续求解 当s第二次被从堆中取出时 (d[s])就是经过点(s)的最小环长度
POJ1734 sightseeing trip
找最小环 并输出一个最小环方案
#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 优化,复杂度可以到 (Oleft(dfrac{n^3}w ight))
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];
POJ1094 sorting it all out
给定 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.
",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;
}
- else
POJ2613 cow relays
给定一张由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;
}
SCOI2008 天平
用floyd跑差分约束==
因为砝码大小只有1、2、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
==就不说了
SPFA 的时间复杂度为(O(kM)(kapprox2))玄学),但理论上界为(O(NM))
SPFA 的优化之SLF即 Small Label First。
即在新元素加入队列时,如果队首元素权值大于新元素权值,那么就把新元素加入队首,否则依然加入队尾。
该优化在确实在一些图上有显著效果,其复杂度也有保证,但是如果有负权边的话,可以直接卡到指数级。
Dijkstra
如果有向图的边权值全为正数,那么有一种复杂度有保证的单源最段路
算法——(Dijkstra) 算法 它的复杂度是(O(|E| log |V|))
事实上,(Dijkstra) 算法的思想和 (Prim) 有很多类似之处 (Dijkstra) 算法
维护了一个未访问的结点集合(T)以及一个从(s)到结点(u)的当前距离 (dist[u])
实现
主要思想是,将结点分成两个集合已确定最短路长度的,未确定的 一开始第一个集合里只有(S)
- 将除源外所有结点当前距离设置为$ ∞(,将源)s$的当前距离设置为(0),
将当前节点设置为源(s)。 - 从当前结点(u)开始,找出所有在未访问集合(T)中与(u)有边((u,v))的结
点(v)。如果(dist[u]+w[u][v]) - 将当前节点从(T)中删除,并且找到在(T)中(dist)最小的结点设置为新的
当前节点。 - 重复((2))和((3))直到(T)成为空集。
时间复杂度:只用分析集合操作, 次 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)) |
P3403 跳楼机
经过改造,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;
}
[USACO08JAN]电话线Telephone Lines
-
法一:二分+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; }
[CQOI2005]新年好
重庆城里有 (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;
}