[UOJ#32][UR#2]跳蚤公路-Bellman-Ford判负环

跳蚤公路

描述

跳蚤国由 n 个城市组成,编号为 1 到 n。

跳蚤国有 m 条连接城市的单向高速公路。经过高速公路当然是要收费的 —— 每条高速公路都有一个过路费 w (货币单位为跳蚤币),司机每次经过这条公路时都需要缴纳 w 跳蚤币。特别地,过路费可以是负的,这表示司机每次经过这条公路时政府都会给 −w 跳蚤币的补贴。

随着时代发展,跳蚤国王认为原有的高速公路规划已经不符合国情了。于是他根据交通拥堵情况把每条公路染成了红、绿、白三色之一,然后他会小心地选定一个数 x,再把每条红色公路的过路费涨 x 跳蚤币,把每条绿色公路的过路费降 x 跳蚤币,而白色公路的过路费不变。

虽然让绿色公路降过路费是好事,但是如果过路费降得太厉害,某个跳蚤司机就可以从 1 号城市出发在城市间转悠,最后停在某个城市 v,数一下自己的钱包发现自己竟然赚钱了。如果赚的钱超过 10100 10 100 跳蚤币,则我们称这样的路径为发财路径。

现在跳蚤国王还不太确定 x 的取值应该是多少,他当然讨厌这种丑陋的靠补贴费发财的行为。于是他希望助手伏特对于 v=1…n,求出使得不存在从 1 号城市出发到 v 号城市结束的发财路径的整数 x 的个数。

除此之外,xx必须介于 1030 − 10 30 到 $10^{30} 之间。

伏特当然知道怎么做啦!但是他想考考你。

输入格式

第一行包括两个正整数 n 和 m。表示跳蚤国的城市数和高速公路数。

接下来 mm 行,每行四个用空格隔开的整数 v,u,w,s (1≤v,u≤n,s∈{−1,0,1}),表示一条从 v 号城市向 u 号城市的过路费为 w 的单向高速公路。如果 s=1 则为红色,s=0 则为白色,s=−1 则为绿色。

输出格式

输出 n 行,第 i 行包含一个整数表示 v=i 时满足条件的 x 的个数。如果 x 的个数超过 1018 10 18 个,请输出 −1。

C/C++ 输入输出 long long 时请用 %lld。C++ 可以直接使用 cin/cout 输入输出。

样例一

input

5 6
1 2 7 0
1 4 3 -1
2 5 4 0
2 3 1 -1
2 3 -3 1
3 2 1 0

output

-1
1
1
-1
1

explanation

对于任何一个 x 都找不到从 1 出发到 1,4 结束的发财路径。

只有当 x=2 时,才找不到从 1 出发到 2,3,5 结束的发财路径。

样例二

input

12 15
1 2 1 0
1 4 1000000000 0
1 6 2 0
2 3 2 0
4 4 -2 1
4 4 10 -1
4 3 1000000000 0
6 7 -5 0
7 8 10 -1
8 6 10 -1
7 9 6 -1
9 10 2 1
10 11 3 1
11 9 5 1
12 12 -233 0

output

-1
-1
9
9
-1
-1
-1
-1
11
11
11
-1

explanation

对于 3,4 号城市,无法发财的 x 的取值范围为 [2,10]。

对于 6,7,8 号城市,无法发财的 x 的取值范围为 [−1030,7.5]。

对于 9,10,11 号城市,无法发财的 x 的取值范围为 [−103,7.5]。由于要求是整数,所以只有介于 −3 到 7 的整数满足条件,共 11 个 x 的取值。

特别注意 1212 号城市,虽然不断地从 1212 号城市出发再回 1212 号城市就可以发财,但是 11 号城市无法到达 1212 号城市,所以所有 xx 都满足条件。

样例三

见样例数据下载。去掉所有的自环后图中不存在环。

样例四

见样例数据下载。

限制与约定

n≤100 m≤10000

时间限制:1s
空间限制:256MB

蒟蒻完全想不到怎么办QAQ
看完题解都不太会写怎么办QAQ

思路:
显然路过负权环的路径才是发财路径……

首先显然会有一个思路:
将环非负的条件表示成形如 kx+b0 k x + b ≥ 0 的形式,然后对于每个节点,将所有经过它的环的的式子的解取交集即可~

然而这本质上是爆枚负环,复杂度爆炸……
于是考虑一种叫Bellman-Ford判负环的东西。

Bellman-Ford的具体原理为,设 f[i][j] f [ i ] [ j ] 代表走 i i 步到达 j j 节点至少所需的最小距离,那么若出现负权环,则会有 f[n][i]<f[n1][i] f [ n ] [ i ] < f [ n − 1 ] [ i ] ,即经过了 n1 n − 1 条以上的边比经过 n1 n − 1 条边距离更短。

对于此题,可以使用类似的方法找负环,只是需要另加一维,令 f[i][j][k] f [ i ] [ j ] [ k ] 代表走 i i 步到达 j j 节点,且经过的特殊边系数之和为 k k ,至少所需的最小距离。
于是,出现负环的条件变成了 kx+f[n][i][k]<jx+f[n1][i][j] k x + f [ n ] [ i ] [ k ] < j x + f [ n − 1 ] [ i ] [ j ]

易知,一个负权环影响的区域是整个联通块。
因此,对于每个联通块中的点,可行的 x x 需要满足的条件为:

mini,k{kx+f[n][i][k]}mini,j{jx+f[n][i][j]} min i , k { k x + f [ n ] [ i ] [ k ] } ≥ min i , j { j x + f [ n ] [ i ] [ j ] }

解这个不等式即可~

怎么解?
首先,对于每个 k k ,枚举所有 j j ,求出下式的解集的补集:

kx+f[n][i][k]minj{jx+f[n][i][j]} k x + f [ n ] [ i ] [ k ] ≥ min j { j x + f [ n ] [ i ] [ j ] }

然后对于得到的所有补集,并在一起并取个补集即可~

代码:

#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

#define f(i,j,k) f[i][j][k+105]

typedef double db;
typedef long long ll;
typedef pair<ll,ll> pr;
const ll N=109;
const ll M=N*N;
const ll Inf=1e18;

ll n,m;
struct o{ll u,v,w,s;}e[M];
vector<pr> vec[N],stk;
ll f[N][N][N<<1];
bool g[N][N];

inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0' || '9'<ch){if(ch=='-')f=-1;ch=getchar();}
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x*f;
}

inline void chkmin(ll &a,ll b){if(a>b)a=b;}
inline void chkmax(ll &a,ll b){if(a<b)a=b;}

int main()
{
    n=read();m=read();
    for(ll i=1;i<=m;i++)
    {
        e[i].u=read();e[i].v=read();
        e[i].w=read();e[i].s=read();
        g[e[i].u][e[i].v]=1;
    }

    for(ll i=1;i<=n;i++)
        g[i][i]=1;

    for(ll k=1;k<=n;k++)
        for(ll i=1;i<=n;i++)
            for(ll j=1;j<=n;j++)
                g[i][j]|=(g[i][k]&g[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(ll i=1;i<=n;i++)
    {
        memcpy(f[i],f[i-1],sizeof(f[i]));
        for(ll j=1;j<=m;j++)
        {
            ll u=e[j].u,v=e[j].v,w=e[j].w,s=e[j].s;
            for(ll k=-n;k<=n;k++)
                if(f(i-1,u,k)<Inf)
                    chkmin(f(i,v,k+s),f(i-1,u,k)+w);
        }
    }

    for(ll i=1;i<=n;i++)
        for(ll k=-n;k<=n;k++)
            if(f(n,i,k)!=Inf)
            {
                ll l=-Inf,r=Inf;
                for(ll j=-n;j<=n;j++)
                    if(f(n-1,i,j)!=Inf)
                    {
                        if(k>j)
                            chkmin(r,(ll)ceil(((db)f(n-1,i,j)-f(n,i,k))/((db)k-j)));
                        else if(k<j)
                            chkmax(l,(ll)floor(((db)f(n-1,i,j)-f(n,i,k))/((db)k-j)));
                        else if(f(n,i,k)>=f(n-1,i,j))
                            goto hell;
                    }
                if(l<r)
                    vec[i].push_back(pr(l,r));
                hell:;
            }

    for(ll i=1;i<=n;i++)
    {
        stk.clear();
        for(ll j=1;j<=n;j++)
            if(g[1][j] && g[j][i])
            {
                for(ll k=0;k<vec[j].size();k++)
                    stk.push_back(vec[j][k]);
            }
        sort(stk.begin(),stk.end());

        ll l=Inf,r=-Inf,lst=-Inf;
        for(ll j=0;j<stk.size();j++)
        {
            if(!j && -Inf<stk[j].first)
            {
                l=-Inf,r=stk[j].first;
                goto heaven;
            }
            if(lst!=-Inf && lst<=stk[j].first)
            {
                l=lst;r=stk[j].first;
                goto heaven;
            }
            chkmax(lst,stk[j].second);
        }
        if(lst<Inf)l=lst,r=Inf;
        heaven:;

        if(l==-Inf || r==Inf || !stk.size())
            puts("-1");
        else
        {
            ll ans=max(r-l+1,0ll);
            printf("%lld\n",ans);
        }
    }

    return 0;
}
    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/zlttttt/article/details/80836986
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞