//主要是根据各种网上资料做笔记
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=1\sim n\) 执行对优化的\(Dijsktra\)求解单源最短路径\(s\)一定为第一个被从堆中取出节点 扫描\(s\)所有出边 扩展、更新完成后 令\(d[s]=+\infty\) 然后继续求解 当s第二次被从堆中取出时 \(d[s]\)就是经过点\(s\)的最小环长度
POJ1734 sightseeing trip
找最小环 并输出一个最小环方案
#include
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];
vectorpath;
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;idis[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
传递闭包
已知一个有向图中任意两点之间是否有连边,要求判断任意两点是否连通
按照 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 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
#include
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;jm) 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
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 天平
bzoj1077 luogu2447
用floyd跑差分约束==
因为砝码大小只有1、2、3 所以未知时最大差值为2 最小差值为-2
由\(A+B>C+D\)可以转为\(A-C>D-B\) 然后就挨个判断就好了
注意判断等于时的条件
#include
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)(k\approx2)\)玄学),但理论上界为\(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 跳楼机
P3403 跳楼机
经过改造,srwudi的跳楼机可以采用以下四种方式移动:
向上移动x层;向上移动y层;向上移动z层;回到第一层。
一个月黑风高的大中午,DJL来到了srwudi的家,现在他在srwudi家的第一层,碰巧跳楼机也在第一层。DJL想知道,他可以乘坐跳楼机前往的楼层数。
yyb:先只考虑只用\(y,z\)两种移动方式,它们一定能够到达一些楼层,
那么这些楼层再只用\(x\)拓展就能够计算答案。
那么我们这样子计算答案,设\(dis[i]\)表示可以到达\(mod\ x=i\)楼层的最小值,
很巧妙!同余最短路
#include
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 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;
}
queueq;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
[国家集训队]墨墨的等式
和跳楼机那题是一样的==
#include
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 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;
}
queueq;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
[USACO08JAN]电话线Telephone Lines
法一:二分+01BFS
很好想der 二分路径中边权从小到大排第k条的值 然后BFS时 不超过mid的边权为0 超过mid的为1二分+01BFS
法二:分层最短路
#include
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 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 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 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
using namespace std;
#define Max(x,y) ((x)>(y)?(x):(y))
#define Min(x,y) ((x)<(y)?(x):(y))
#define ll long long
typedef pairpii;
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 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,greater >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;
}