【UOJ#32】【UR#2】跳蚤公路

最短路,负环,不等式计算

题目大意:

给定一张有向图,每条边有三种颜色(红,白,绿)以及边权。

现在要选定整数 $x$,使每条红边的边权增加 $x$,每条绿边的边权减少 $x$。

要求对于所有 $v\in [1,n]$,求出有多少个整数 $x$,满足从 $1$ 到 $v$ 存在一条路径,使得边权和为负无穷大(即经过了负环)。

若满足条件的 $x$ 超过 $10^{30}$ 个(可以理解为无穷多个),则输出 -1

题解:

在没有负环的图中,两点间的最短路最多只会经过一个点 $1$ 次,即最多经过 $n-1$ 条边。

Bellman-Ford 算法判断负环的原理就是,每个点松弛 $n-1$ 次后,若还能进行第 $n$ 次松弛来更新最短路,即说明存在负环。

令 $f[i][u][k]$ 表示 $x=0$ 时,经过了最多 $i$ 条边,从 $1$ 号点到 $u$ 号点,经过的红边减去绿边条数为 $k$ 时的最短路。

这个 $f$ 可以通过 Bellman-Ford 算法在 $O(n^4)$ 的时间复杂度内求出。

那么经过 $i$ 条边时,实际的最短路就可以表示为 $kx+f[i][u][k]$。

那么对于点 $u$,若 $u$ 不在负环上,则必然满足:

考虑求它的解集的补集,即 $u$ 在负环上的情况。

那么相当于对一个 $v$ ,满足 $\min\{kx+f[n-1][u][k]\}\leq vx+f[n][u][v]$,对每个 $k$ 求出的解求交即可。注意上下取整的问题。

然后再对每个 $v$ 求得的解集求并集,就是 $u$ 在负环上时的 $x$ 的解。

然后这个负环要更新到其他的点。$u$ 能到的所有点都可以经过这个负环,所以对于 $u$ 求出的 $x$ 的解要更新到 $u$ 能到的点。

对于两点间的连通性只需要一次传递闭包即可,最后对每个点求一个线段覆盖长度的问题。

算法的总时间复杂度 $O(n^4)$。

Code:

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<vector>
#include<cstring>
using namespace std;
typedef long long LL;
typedef pair<LL,LL>PII;
#define f(a,b,c) F[a][b][(c)+102]
const int N=105;
const LL inf=2e18;
struct edge{
    int to,nxt,w,s;
}e[N*N];
int head[N],cnt,n,m;
bool go[N][N];
LL F[N][N][2*N];
vector<PII>sg[N];
PII calc(int a,LL b){
    if(a==0){
        if(b>0)return make_pair(-inf,inf);
        return make_pair(inf,-inf);
    }
    if(a>0){
        LL up=b/a;
        while(up*a>=b)--up;
        return make_pair(-inf,up); 
    }
    b=-b,a=-a;
    LL dn=b/a-1;
    while(dn*a<=b)++dn;
    return make_pair(dn,inf);
}
inline PII un(const PII&a,const PII&b){
    return make_pair(max(a.first,b.first),min(a.second,b.second));
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    for(cin>>n>>m;m--;){
        int u,v,w,s;
        cin>>u>>v>>w>>s;
        e[++cnt]=(edge){v,head[u],w,s},head[u]=cnt;
        go[u][v]=1;
    }
    for(int i=1;i<=n;++i)go[i][i]=1;
    for(int k=1;k<=n;++k)
    for(int i=1;i<=n;++i)
    for(int j=1;j<=n;++j)
    go[i][j]|=go[i][k]&&go[k][j];
    for(int i=0;i<=n;++i)
    for(int j=1;j<=n;++j)
    for(int k=-n;k<=n;++k)f(i,j,k)=inf;
    f(0,1,0)=0;
    for(int t=0;t<n;++t){
        memcpy(F[t+1],F[t],sizeof*F);
        for(int i=1;i<=n;++i)
        for(int j=head[i];j;j=e[j].nxt){
            const int to=e[j].to;
            for(int k=-n;k<=n;++k)
            if(f(t,i,k)<inf)
            f(t+1,to,k+e[j].s)=min(f(t+1,to,k+e[j].s),f(t,i,k)+e[j].w);
        }
    }
    for(int x=1;x<=n;++x)if(go[1][x]){
        vector<PII>d;
        for(int k0=-n;k0<=n;++k0)if(f(n,x,k0)<inf){
            PII vc=make_pair(-inf,inf);
            for(int k1=-n;k1<=n;++k1)
            if(f(n-1,x,k1)<inf)
            vc=un(vc,calc(k0-k1,f(n-1,x,k1)-f(n,x,k0)));
            if(vc.first<=vc.second)d.push_back(vc);
        }
        for(int y=1;y<=n;++y)
        if(go[x][y]){
            for(int i=0;i<(int)d.size();++i)
            sg[y].push_back(d[i]);
        }
    }
    for(int x=1;x<=n;++x){
        if(!go[1][x]){cout<<"-1\n";continue;}
        if(sg[x].empty()){cout<<"-1\n";continue;}
        sort(sg[x].begin(),sg[x].end());
        LL ans=0;
        PII nw=sg[x][0];
        for(int i=1;i<(int)sg[x].size();++i){
            PII nx=sg[x][i];
            if(nx.first>nw.second){
                ans+=nw.second-nw.first+1;
                nw=nx;
            }else nw.second=max(nw.second,nx.second);
        }
        ans+=nw.second-nw.first+1;
        ans=2*inf+1-ans;
        cout<<(ans>1e18?-1:ans)<<'\n';
    }
    return 0;
}