这里是引用
文章目录
- 写在前面
- 要说floyd就不能只说板子
- 状态
- 转移
- 空间优化
- kij or ijk
- 应用
- 求多源最短路
- 求传递闭包
- 求无向图的最小环
写在前面
在做洛谷的树上dp题单的时候遇到了一道题目P1613 跑路发现自己对flyod的理解太浅薄了,于是去重新学习了一遍,又做了几道题目,然后结合了acwing的算法提高课的总结,于是乎有了这篇博客。
要说floyd就不能只说板子
什么是floyd,提到floyd肯定就会想到最短路。
Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。
我们现在看到的形式
for(int k=1;k<=n;k++){
for(int i=1;i<=n;++j){
for(int k=1;k<=n;++k){
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
现在我们的板子中经常看到的floyd都是二维,其实最原始的状态是三维。
状态
d [ k ] [ i ] [ j ] d[k][i][j] d[k][i][j]:从 i i i点到 j j j点只经过,前k个结点 ( 1 、 2 、 3 、 . . . 、 k − 1 、 k ) (1、2、3、...、k-1、k) (1、2、3、...、k−1、k)的最短距离
转移
转移的时候是以经不经过第
k
k
k个结点来划分进行转移的。
不经过:
d
[
k
]
[
i
]
[
j
]
=
d
[
k
−
1
]
[
i
]
[
j
]
d[k][i][j]=d[k-1][i][j]
d[k][i][j]=d[k−1][i][j]
经过:
d
[
k
]
[
i
]
[
j
]
=
d
[
k
−
1
]
[
i
]
[
k
]
+
d
[
k
−
1
]
[
j
]
[
k
]
d[k][i][j]=d[k-1][i][k]+d[k-1][j][k]
d[k][i][j]=d[k−1][i][k]+d[k−1][j][k]
所以状态转移方程为:
d
[
k
]
[
i
]
[
j
]
=
m
i
n
(
d
[
k
−
1
]
[
i
]
[
j
]
,
d
[
k
−
1
]
[
i
]
[
k
]
+
d
[
k
−
1
]
[
j
]
[
k
]
)
d[k][i][j]=min(d[k-1][i][j],d[k-1][i][k]+d[k-1][j][k])
d[k][i][j]=min(d[k−1][i][j],d[k−1][i][k]+d[k−1][j][k])
空间优化
观察上面的状态转移方程可以看到在算第
k
k
k层的状态时,只用到了
k
−
1
k-1
k−1层的状态,这就启发了我们是不是可以像背包问题那样,优化掉一维的空间。
我们可以看一下去掉第一维会不会得到的方程是否和原方程等价.
也就是说我们在去点第一维后,在算第
k
k
k层的d[i][j]时
d
[
i
]
[
k
]
d[i][k]
d[i][k]和
d
[
j
]
[
k
]
d[j][k]
d[j][k]中存的仍然是第
k
−
1
k-1
k−1层的结果.
是不是这样呢?
要想看
d
[
i
]
[
k
]
d[i][k]
d[i][k]和
d
[
j
]
[
k
]
d[j][k]
d[j][k]中存的是不是第
k
−
1
k-1
k−1层的结果.我们可以考虑
d
[
i
]
[
k
]
d[i][k]
d[i][k]和
d
[
j
]
[
k
]
d[j][k]
d[j][k]在k层时什么时候会更新
先考虑
d
[
i
]
[
k
]
d[i][k]
d[i][k]什么时候更新
d
[
k
]
[
i
]
[
j
]
=
m
i
n
(
d
[
k
−
1
]
[
i
]
[
j
]
,
d
[
k
−
1
]
[
i
]
[
k
]
+
d
[
k
−
1
]
[
j
]
[
k
]
)
d[k][i][j]=min(d[k-1][i][j],d[k-1][i][k]+d[k-1][j][k])
d[k][i][j]=min(d[k−1][i][j],d[k−1][i][k]+d[k−1][j][k])
当
j
=
k
j=k
j=k时显然第
k
−
1
k-1
k−1层的
d
[
i
]
[
k
]
d[i][k]
d[i][k]会更新,我们把
j
=
k
j=k
j=k带入有=d[k-1][i][k]
d
[
k
]
[
i
]
[
k
]
=
m
i
n
(
d
[
k
−
1
]
[
i
]
[
j
]
,
d
[
k
−
1
]
[
i
]
[
k
]
+
d
[
k
−
1
]
[
k
]
[
k
]
)
d[k][i][k]=min(d[k-1][i][j],d[k-1][i][k]+d[k-1][k][k])
d[k][i][k]=min(d[k−1][i][j],d[k−1][i][k]+d[k−1][k][k])
由于
d
[
k
−
1
]
[
k
]
[
k
]
=
0
d[k-1][k][k]=0
d[k−1][k][k]=0,有
d
[
k
]
[
i
]
[
k
]
=
d
[
k
−
1
]
[
i
]
[
k
]
d[k][i][k]=d[k-1][i][k]
d[k][i][k]=d[k−1][i][k]
所以这是等价变形的.
然后可以考虑
i
=
k
i=k
i=k时,情况是一样的.
然后我们就可以证明这两个状态转移方程是等价的.也就有了新的状态转移方程
d
[
i
]
[
j
]
=
m
i
n
(
d
[
i
]
[
j
]
,
d
[
i
]
[
k
]
+
d
[
k
]
[
j
]
)
d[i][j]=min(d[i][j],d[i][k]+d[k][j])
d[i][j]=min(d[i][j],d[i][k]+d[k][j])
kij or ijk
从dp的角度考虑也就很自然的会得出循环k在在最外层.
具体的为什么在最外层知乎上已经很多回答了.可以看下面的链接
为什么是kij不是ijk
应用
求多源最短路
AcWing 1125. 牛的旅行
题意是给了一堆牧场
#include <bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i = (a); i <= (b); ++i)
#define fep(i,a,b) for(int i = (a); i >= (b); --i)
#define pii pair<int, int>
#define pdd pair<double,double>
#define ll long long
#define db double
#define endl '\n'
#define x first
#define y second
#define pb push_back
#define vi vector<int>
using namespace std;
int n,m;
const int N=200;
const db inf=1e20;
db d[N][N],mv[N];
pdd a[N];
char g[N][N];
void solve() {
cin>>n;
rep(i,1,n) {
cin>>a[i].x>>a[i].y;
}
rep(i,1,n) {
cin>>g[i]+1;
}
auto dist=[&](pdd a, pdd b) {
db dx=a.x-b.x;
db dy=a.y-b.y;
return sqrt(dx*dx+dy*dy);
};
rep(i,1,n) {
rep(j,1,n) {
if(i==j) {
d[i][j]=0;
} else if(g[i][j]=='1') {
d[i][j]=dist(a[i],a[j]);
} else {
d[i][j]=inf;
}
}
}
//跑一遍floyd
rep(k,1,n) {
rep(i,1,n) {
rep(j,1,n) {
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
//预处理出来每个点到连通块其他点的最短路的最大值
db ans1=0,ans2=inf;
rep(i,1,n) {
rep(j,1,n) {
if(d[i][j]<inf) {
mv[i]=max(mv[i],d[i][j]);
}
}
ans1=max(ans1,mv[i]);
}
// cout<<ans2<<endl;
rep(i,1,n) {
rep(j,1,n) {
if(d[i][j]>=inf) {
ans2=min(ans2,mv[i]+mv[j]+dist(a[i],a[j]));
}
}
}
cout<<setiosflags(ios::fixed)<<setiosflags(ios::right)<<setprecision(6)<<max(ans1,ans2)<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// freopen("1.in", "r", stdin);
int _;
// cin>>_;
// while(_--)
solve();
return 0;
}
求传递闭包
传递闭包
:在有向图中,如果a->b,并且b->c那么a->c.换句话说将所有能间接到达的点都连上直接的边后的图就是原图的传递闭包
oiwiki上面用bitset进行的优化
AcWing 343. 排序
思路:
时间复杂度:
O
(
m
n
3
)
O(mn^3)
O(mn3)
代码有点长
#include <bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i = (a); i <= (b); ++i)
#define fep(i,a,b) for(int i = (a); i >= (b); --i)
#define pii pair<int, int>
#define pdd pair<double,double>
#define ll long long
#define db double
#define endl '\n'
#define x first
#define y second
#define pb push_back
#define vi vector<int>
using namespace std;
void solve() {
int n,m;
while(cin>>n>>m,n||m){
vector<vector<int>>g(n+1,vector<int>(n+1));
vector<vector<int>>d(n+1,vector<int>(n+1));
auto floyd=[&](){
rep(k,0,n-1){
rep(i,0,n-1){
rep(j,0,n-1){
d[i][j]|=d[i][k]&&d[k][j];
}
}
}
};
auto check=[&](){
rep(i,0,n-1){
if(d[i][i]) return 2;
}
rep(i,0,n-1){
rep(j,0,i-1){
if(!d[i][j]&&!d[j][i]){
return 0;
}
}
}
return 1;
};
int type=0,turn=0;
rep(i,1,m){
string s;
cin>>s;
int u=s[0]-'A',v=s[2]-'A';
if(!type){
d[u][v]=1;
floyd();
type=check();
if(type){
turn=i;
}
}
}
if(!type){
cout<<"Sorted sequence cannot be determined."<<endl;
}else if(type==2){
cout<<"Inconsistency found after "<<turn<<" relations."<<endl;
}else{
vector<int>st(n+1,0);
auto getmn=[&](){
rep(i,0,n-1){
if(!st[i]){
int flag=0;
rep(j,0,n-1){
if(!st[j]&&d[j][i]){
flag=1;
break;
}
}
if(!flag){
st[i]=1;
char c='A'+i;
return c;
}
}
}
};
cout<<"Sorted sequence determined after "<<turn<<" relations: ";
rep(i,0,n-1){
cout<<getmn();
}
cout<<"."<<endl;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// freopen("1.in", "r", stdin);
int _;
// cin>>_;
// while(_--)
solve();
return 0;
}
考虑加上优化
每加入一条边之后其实并不需要再重新跑一遍
f
l
o
y
d
floyd
floyd,我们只需要考虑这条新加入的边会产生什么影响即可。
时间复杂度:
O
(
m
n
2
)
O(mn^2)
O(mn2)
#include <bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i = (a); i <= (b); ++i)
#define fep(i,a,b) for(int i = (a); i >= (b); --i)
#define pii pair<int, int>
#define pdd pair<double,double>
#define ll long long
#define db double
#define endl '\n'
#define x first
#define y second
#define pb push_back
#define vi vector<int>
using namespace std;
void solve() {
int n,m;
while(cin>>n>>m,n||m){
vector<vector<int>>g(n+1,vector<int>(n+1));
vector<vector<int>>d(n+1,vector<int>(n+1));
auto check=[&](){
rep(i,0,n-1){
if(d[i][i]) return 2;
}
rep(i,0,n-1){
rep(j,0,i-1){
if(!d[i][j]&&!d[j][i]){
return 0;
}
}
}
return 1;
};
int type=0,turn=0;
rep(i,1,m){
string s;
cin>>s;
int u=s[0]-'A',v=s[2]-'A';
if(!type){
d[u][v]=1;
auto kkk=[&](){
rep(i,0,n-1){
//处理能到达v能到的所有点
if(d[v][i]) d[u][i]=1;
//所有能到u的点都能到v
if(d[i][u]) d[i][v]=1;
//所有能到u的点不包含u
if(d[i][u]){
rep(j,0,n-1){
//所有v能到的点不包含v
if(d[v][j]){
d[i][j]=1;
}
}
}
}
};
kkk();
type=check();
if(type){
turn=i;
}
}
}
if(!type){
cout<<"Sorted sequence cannot be determined."<<endl;
}else if(type==2){
cout<<"Inconsistency found after "<<turn<<" relations."<<endl;
}else{
vector<int>st(n+1,0);
auto getmn=[&](){
rep(i,0,n-1){
if(!st[i]){
int flag=0;
rep(j,0,n-1){
if(!st[j]&&d[j][i]){
flag=1;
break;
}
}
if(!flag){
st[i]=1;
char c='A'+i;
return c;
}
}
}
};
cout<<"Sorted sequence determined after "<<turn<<" relations: ";
rep(i,0,n-1){
cout<<getmn();
}
cout<<"."<<endl;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// freopen("1.in", "r", stdin);
int _;
// cin>>_;
// while(_--)
solve();
return 0;
}
求无向图的最小环
AcWing 344. 观光之旅
oiwiki上面无向图找环说的很清楚。
、
关于这道题目的一个难点是记录状态。
#include <bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i = (a); i <= (b); ++i)
#define fep(i,a,b) for(int i = (a); i >= (b); --i)
#define pii pair<int, int>
#define pdd pair<double,double>
#define ll long long
#define db double
#define endl '\n'
#define x first
#define y second
#define pb push_back
#define vi vector<int>
using namespace std;
void solve() {
int n,m;
cin>>n>>m;
//g用于存原图,因为求最小环的时候需要原图中的边长
vector<vi>g(n+1,vi(n+1,0x3f3f3f3f));
vector<vi>pos(n+1,vi(n+1));
//d用于做floyd的最短路径
rep(i,1,m){
int u,v,w;
cin>>u>>v>>w;
g[u][v]=g[v][u]=min(g[u][v],w);
}
vector<vi>d=g;
int res=0x3f3f3f3f;
vi ans;
auto dfs=[&](auto &&dfs,int i, int j)->void{
if(!pos[i][j]) return;
int k=pos[i][j];
dfs(dfs,i,k);
ans.pb(k);
dfs(dfs,k,j);
};
auto get=[&](int i, int j,int k){
ans.clear();
ans.pb(k);
ans.pb(i);
dfs(dfs,i,j);
ans.pb(j);
};
rep(k,1,n){
//枚举i,j找最小环
rep(i,1,k-1){
rep(j,i+1,k-1){
if(res>d[i][j]+g[i][k]+g[k][j]){
res=d[i][j]+g[i][k]+g[k][j];
get(i,j,k);
}
}
}
//正常floyd
rep(i,1,n){
rep(j,1,n){
if(d[i][j]>d[i][k]+d[k][j]){
d[i][j]=d[i][k]+d[k][j];
pos[i][j]=k;
}
}
}
}
if(res==0x3f3f3f3f){
cout<<"No solution."<<endl;
}else{
for(auto it:ans){
cout<<it<<' ';
}
cout<<endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// freopen("1.in", "r", stdin);
int _;
// cin>>_;
// while(_--)
solve();
return 0;
}