此笔记开始记录时间为2019.7.20
主要供自己深化知识点
一、Floyd算法:
流程(重点理解):
通常我们写的Floyd算法框架是这样的
for(int k=1;k<=n;++k)
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
Floyd算法能如此优美是由于其为dp算法
式子如此简单以至于我们能轻而易举将其背下
大部分人对其了解只限于——三重循环 k 放最外层 i j 放内层
但是这个算法并不是这么的显然
首先
说这是个dp算法
因为存在转移方程——d[i][j]=min(d[i][j],d[i][k]+d[k][j])
可以感性理解一下:
对不起我太菜了
稍稍证明一下:
设d[i][j][k]为i->j路径只经过1-k节点的最短路
且d[i][j][0]为i->j的初始路径
则若经过k节点
d[i][j][k]=d[i][k][k-1]+d[k][j][k-1]
否则
d[i][j][k]=d[i][j][k-1]
最后滚动数组就可以得出最终式了
用法总结:
-
最短路:最基本用法,套框架即可
-
传递闭包:
通过Floyd确定各两者的二元关系
经典例题:
POJ3660 Cow Contest
题目大意:有N头牛,评以N个等级,各不相同,先给出部分牛的等级的高低关系,问最多能确定多少头牛的等级
解题思路:一头牛的等级,当且仅当它与其它N-1头牛的关系确定时确定,于是我们可以将牛的等级关系看做一张有向图,然后跑Floyd,得到任意两点的关系,再对每一头牛进行检查即可
- 无向图最小环:
运用Floyd求无向图中的最小环
例题:
POJ1734 Sightseeing trip
题目大意:给一张图,求图中长度最小的环,输出环中节点
解题思路:最小环长度为min{d[i][j]+e[i][k]+e[k][j]}(执行此步时,要在更新d[i][j]前)
p.s. 求最小环还有一个更优秀的算法:
先求最小生成树 枚举边 用lca处理 算出最小环
- 求无向图删除一些边,使最短路不变,最多可删多少边
这个好理解
若i-j有一条边
且i-j存在一条路径长度小于该边
则该边可删除
题目可以自己找找
附上代码:
POJ3660 Cow Contest
//poj 3660 cow contest
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#define inf 100000000
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dwn(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
typedef long long ll;
int n,m,ans=0;
bool d[110][110];
void floyd(int n)
{
rep(k,1,n)
rep(i,1,n)
rep(j,1,n)
d[i][j]|=d[i][k]&d[k][j];
}
int main()
{
scanf("%d%d",&n,&m);
rep(i,1,m)
{
int u,v;
scanf("%d%d",&u,&v);
d[u][v]=1;
}
floyd(n);
rep(i,1,n){
rep(j,1,n)
{
if(i==j) continue;
if((!(d[i][j]||d[j][i]))||(d[i][j]&&d[j][i])){ans++;break;}
}
}
printf("%d
",n-ans);
return 0;
}
POJ1734 Sightseeing Trip
//poj 1734 sightseeing trip
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#define inf 0x1f1f1f1f
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dwn(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
typedef long long ll;
int n,m,ans,cnt,way[110],d[110][110],e[110][110],path[110][110];
void floyd()
{
rep(k,1,n)
{
rep(i,1,k-1)
{
rep(j,i+1,k-1)
{
if(ans>d[i][j]+e[i][k]+e[k][j])
{
ans=d[i][j]+e[i][k]+e[k][j];
cnt=0;
int t=j;
while(t!=i)
{
way[++cnt]=t;
t=path[i][t];
}
way[++cnt]=i;
way[++cnt]=k;//此时就要把路径记录下来 因为path会在循环中变化
}
}
}
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];
path[i][j]=path[k][j];//path i j 表示此时从i到j的路j的前一节点
}
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF){
ans=inf;
memset(d,0x1f,sizeof(d));
memset(e,0x1f,sizeof(e));
rep(i,1,n)
{
e[i][i]=d[i][i]=0;
rep(j,1,n)
{
path[i][j]=i;
}
}
rep(i,1,m)
{
int u,v,dis;
scanf("%d%d%d",&u,&v,&dis);
d[v][u]=d[u][v]=e[v][u]=e[u][v]=min(e[u][v],dis);
}
floyd();
if(ans==inf)
printf("No solution.
");
else
{
rep(i,1,cnt)
{
printf("%d ",way[i]);
}
printf("
");
}
}
return 0;
}
二、差分约束系统
概念:
对于一组不等式组:
这样的不等式组称为差分约束系统
要么有无数解 要么无解
因为若存在一组解{x1,x2,x3,...}
则必定存在解{x1+k,x2+k,x3+k,...}
解法:
首先引入三角形不等式
对于有向边<u,v>
若dis[x]为源点到x点最短路
则有(dis[v]le dis[u]+edge(u,v))
我们发现这个式子可转化为
(dis[v]-dis[u]le edge(u,v))
与不等式组中式子模式很相似
于是我们可以利用这个性质
构建图形解决这种问题
构图:
即对于(xv-xule k)构建一条<u,v>有向边 权值为k
求一遍最短路即可得到一种解
当且仅当图中存在负环时 无解
判负环:当一个点入队次数>所有点个数 存在负环
选取源点:
选取源点太麻烦了
于是我们新增一个源点x0
在不等式组中加上一堆条件
若设x0初始值为k
则答案均小于等于k
且所有点的dis值都能求出
解决:
最后用spfa求最短路即为一组解
注意判负环
对于另一类问题:
可以两边乘-1
就变成第一种问题
但是比较麻烦
发现可以用最长路的三角形不等式构图
即对于有向边<u,v>,dis[x]表示源点到x点的最长路
则dis[v]>=dis[u]+edge(u,v)
同理用spfa求最长路 判正环
值得一提的是
最短路求出的是不超过x0的最大值
最长路求出的是不低于x0的最小值
在下面一道题中读者能有更深刻的体会
例题:
思路:
由于每个人都要有糖所以源点值设为1,保证每个人糖数至少1
再跑一遍最长路累加答案就行了
求出来肯定是最小值是因为这是满足条件的dis的最小值
p.s. 这道题其实正解不是spfa 而是 tarjan缩点+拓扑排序
spfa法要加特判,特判自环有长度大于0的自环直接输出-1
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
#define inf 2000000000
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dwn(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
typedef long long ll;
int n,m,dis[100010],cnt[100010];
bool v[100010];
int tot=1,head[100010];
struct EDGE
{
int to,nxt,d;
}edge[200010];
ll ans=0;
queue<int> que;
void add(int u,int v,int d)
{
edge[++tot].to=v;
edge[tot].nxt=head[u];
edge[tot].d=d;
head[u]=tot;
}
bool spfa()
{
rep(i,1,n)
dis[i]=1,v[i]=1,que.push(i);
while(!que.empty())
{
int x=que.front();v[x]=0;que.pop();
for(int i=head[x];i;i=edge[i].nxt)
{
int y=edge[i].to;
if(dis[y]<dis[x]+edge[i].d)
{
dis[y]=dis[x]+edge[i].d;
if(!v[y])
{
v[y]=1;
que.push(y);
if(++cnt[y]>n) return 0;
}
}
}
}
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
rep(i,1,m)
{
int x,a,b;
scanf("%d%d%d",&x,&a,&b);
if(x==1) add(a,b,0),add(b,a,0);
if(x==2) add(a,b,1);
if(x==3) add(b,a,0);
if(x==4) add(b,a,1);
if(x==5) add(a,b,0);
if(x%2==0&&a==b){
printf("-1
");
return 0;
}
}
if(spfa())
{
rep(i,1,n)
ans+=dis[i];
printf("%lld
",ans);
}
else
{
printf("-1
");
}
return 0;
}