笔记

此笔记开始记录时间为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]
最后滚动数组就可以得出最终式了

严格证明 这篇讲的真的很好
dp最初形式

用法总结:

  1. 最短路:最基本用法,套框架即可

  2. 传递闭包:
    通过Floyd确定各两者的二元关系
    经典例题:

POJ3660 Cow Contest
题目大意:有N头牛,评以N个等级,各不相同,先给出部分牛的等级的高低关系,问最多能确定多少头牛的等级
解题思路:一头牛的等级,当且仅当它与其它N-1头牛的关系确定时确定,于是我们可以将牛的等级关系看做一张有向图,然后跑Floyd,得到任意两点的关系,再对每一头牛进行检查即可

  1. 无向图最小环:
    运用Floyd求无向图中的最小环
    例题:

POJ1734 Sightseeing trip
题目大意:给一张图,求图中长度最小的环,输出环中节点
解题思路:最小环长度为min{d[i][j]+e[i][k]+e[k][j]}(执行此步时,要在更新d[i][j]前)
p.s. 求最小环还有一个更优秀的算法:
先求最小生成树 枚举边 用lca处理 算出最小环

  1. 求无向图删除一些边,使最短路不变,最多可删多少边
    这个好理解
    若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;
}

二、差分约束系统

概念:

对于一组不等式组:

[egin{cases} x1-x2le -1\ x2-x3le 3\ x1-x4le 4\ x4-x3le 1\ x5-x4le -4\ x1-x5le 2\ x3-x5le 4\ x2-x5le -3\ end{cases} ]

这样的不等式组称为差分约束系统
要么有无数解 要么无解
因为若存在一组解{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
在不等式组中加上一堆条件

[egin{cases} x1-x0le 0\ x2-x0le 0\ ......\ xn-x0le 0 end{cases} ]

若设x0初始值为k
则答案均小于等于k
且所有点的dis值都能求出

解决:

最后用spfa求最短路即为一组解
注意判负环

对于另一类问题:

[egin{cases} x1-x2ge a1\ x2-x3ge a2\ ... end{cases} ]

可以两边乘-1
就变成第一种问题

但是比较麻烦
发现可以用最长路的三角形不等式构图
即对于有向边<u,v>,dis[x]表示源点到x点的最长路
则dis[v]>=dis[u]+edge(u,v)
同理用spfa求最长路 判正环

值得一提的是
最短路求出的是不超过x0的最大值
最长路求出的是不低于x0的最小值
在下面一道题中读者能有更深刻的体会

例题:

[SCOI2011]糖果

思路:
由于每个人都要有糖所以源点值设为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;
}

留个大坑 有时间更

原文地址:https://www.cnblogs.com/MYsBlogs/p/11219805.html