【UR #2】跳蚤公路

【UR #2】跳蚤公路

参照yjc方法。也就是地铁环线那个题。

求每个点不在负环内的x的取值范围。然后所有1到j能到i的j的范围取交。得到答案。

每个边形如kx+b的直线,每个环也是

每个点不在负环内的x取值范围是区间

 

两次二分,

第一次二分区间左端点,第二次右端点。

如果没有负环,左端点往左偏,右端点往右偏

否则,记录负环的构成:k*mid+b的k的正负,可以得到mid应该往哪里偏。

 

注意SPFA找负环:

记录has[x]表示到x的最短路已经经过了多少个点,

dis[x]最短路,fr[x]是最短路的前驱,pre[x]是最短路前驱指向x的边

发现has[x]>n的时候,证明出现了负环。但是x不一定在负环上

不断跳fr[x]找到整个环重复的第一个点z。

再fr[z]找到整个环。

 

emmm,一个问题是,负环上的点不会被其他点松弛导致fr[*]找不到负环吗?

 

由于SPFA的BFS性质,以及has[x]>n才会判断出有负环,

所以整个负环上的点,在判断has[*]>n之前,要么不会被松弛、或者松弛后要么找到新的负环、要么会被这个负环再次松弛,

总之这个环确实能找出来。

 

代码:

目前(2019.6.17)UOJ最优解

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define fi first
#define se second
#define mk(a,b) make_pair(a,b)
#define numb (ch^'0')
#define pb push_back
#define solid const auto &
#define enter cout<<endl
#define pii pair<int,int>
using namespace std;
typedef long long ll;
template<class T>il void rd(T &x){
    char ch;x=0;bool fl=false;while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);(fl==true)&&(x=-x);}
template<class T>il void output(T x){if(x/10)output(x/10);putchar(x%10+'0');}
template<class T>il void ot(T x){if(x<0) putchar('-'),x=-x;output(x);putchar(' ');}
template<class T>il void prt(T a[],int st,int nd){for(reg i=st;i<=nd;++i) ot(a[i]);putchar('\n');}
namespace Modulo{
const int mod=998244353;
il int ad(int x,int y){return x+y>=mod?x+y-mod:x+y;}
il int sub(int x,int y){return ad(x,mod-y);}
il int mul(int x,int y){return (ll)x*y%mod;}
il void inc(int &x,int y){x=ad(x,y);}
il void inc2(int &x,int y){x=mul(x,y);}
il int qm(int x,int y=mod-2){int ret=1;while(y){if(y&1) ret=mul(x,ret);x=mul(x,x);y>>=1;}return ret;}
template<class ...Args>il int ad(const int a,const int b,const Args &...args) {return ad(ad(a,b),args...);}
template<class ...Args>il int mul(const int a,const int b,const Args &...args) {return mul(mul(a,b),args...);}
}
// using namespace Modulo;
namespace Miracle{
const int N=105;
const int M=10005;
const ll inf=1e14;
int n,m;
struct edge{
    int x,y,k,b;
}b[M];
struct node{
    int nxt,to;
    int k,b;
    ll val(ll x){
        return k*x+b;
    }
}e[2*M];
int hd[N],cnt;
void add(int x,int y,int k,int b){
    e[++cnt].nxt=hd[x];
    e[cnt].to=y;
    e[cnt].k=k;e[cnt].b=b;
    hd[x]=cnt;
}
int c[N],df,dfn[N],low[N];
int scc;
int f[N][N];
int sta[N],top,in[N];

int sz[N];
void tarjan(int x){
    dfn[x]=low[x]=++df;
    sta[++top]=x;in[x]=1;
    for(reg i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(!dfn[y]){
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }else if(in[y]) low[x]=min(low[x],dfn[y]);
    }
    if(low[x]==dfn[x]){
        ++scc;
        int z;
        do{
            z=sta[top--];
            in[z]=0;
            c[z]=scc;
            ++sz[scc];
        }while(z!=x);
    }
}
struct seg{
    ll l,r;
    seg(){l=-inf,r=inf;}
    seg(ll le,ll ri){
        l=le;r=ri;
    }
    bool empty(){
        return l>r;
    }
    bool full(){
        return (l==-inf)&&(r==inf);
    }
    seg friend operator &(seg a,seg b){
        return seg(max(a.l,b.l),min(a.r,b.r));
    }
    seg friend operator |(seg a,seg b){
        if(a.empty()) return b;
        if(b.empty()) return a;
        return seg(min(a.l,b.l),max(a.r,b.r));
    }
}lim[N];
ll dis[N];
int pre[N];
int fr[N];
int has[N];
queue<int>q;
bool vis[N];
int spfa(int s,ll mid,int n){
    //-1 need small; 1: need big ;0 ok
    while(!q.empty()) q.pop();
    memset(dis,0x3f,sizeof dis);
    memset(vis,0,sizeof vis);
    // memset(pre,0,sizeof pre);
    // memset(has,0,sizeof has);

    dis[s]=0;has[s]=1;
    q.push(s);
    while(!q.empty()){
        int x=q.front();q.pop();
        // cout<<" xx "<<x<<endl;
        vis[x]=0;
        
        for(reg i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            if(dis[y]>dis[x]+e[i].val(mid)){
                dis[y]=dis[x]+e[i].val(mid);
                pre[y]=i;
                fr[y]=x;
                has[y]=has[x]+1;

                if(has[y]>n){//has fuhuan 
                    // cout<<" fuhuan !!!"<<endl;
                    int z=y;
                    int k=0;
                    memset(vis,0,sizeof vis);
                    do{
                        // cout<<" zz "<<z<<endl;
                        // k+=e[pre[z]].k;
                        vis[z]=1;
                        z=fr[z];
                    }while(!vis[z]);

                    int lp=z;
                    do{
                        k+=e[pre[z]].k;
                        z=fr[z];
                    }while(z!=lp);

                    if(k<0){
                        return -1;
                    }else{
                        return 1;
                    }
                }

                if(!vis[y]){
                    vis[y]=1;
                    q.push(y);
                }
            }
        }
    }
    return 0;
}
void calc(int s,int id){
    // cout<<" calc "<<id<<" s "<<s<<endl;
    ll al=inf+1;
    ll L=-inf,R=inf;
    while(L<=R){
        ll mid=(L+R)>>1;
        // cout<<L<<" "<<R<<" : "<<mid<<endl;
        int lp=spfa(s,mid,sz[id]);
        if(lp==-1){
            R=mid-1;
        }else if(lp==1){
            L=mid+1;
        }else{
            al=mid;
            R=mid-1;
        }
    }
    // cout<<" al "<<al<<endl;
    ll ar=-inf-1;
    L=-inf,R=inf;
    while(L<=R){
        ll mid=(L+R)>>1;
        
        int lp=spfa(s,mid,sz[id]);
        if(lp==-1){
            R=mid-1;
        }else if(lp==1){
            L=mid+1;
        }else{
            ar=mid;
            L=mid+1;
        }
    }
    // cout<<" ar "<<ar<<endl;
    lim[id]=seg(al,ar);
}
int main(){
    rd(n);rd(m);
    for(reg i=1;i<=m;++i){
        rd(b[i].x);rd(b[i].y);rd(b[i].b);rd(b[i].k);
        add(b[i].x,b[i].y,233,233);
        // f[b[i].x][b[i].y]=1;
    }
    for(reg i=1;i<=n;++i){
        if(!dfn[i]){
            tarjan(i);
        }
    }
    // cout<<" after tarjan "<<endl;
    for(reg i=1;i<=scc;++i){
        int s=0;
        memset(hd,0,sizeof hd);
        cnt=0;
        for(reg j=1;j<=m;++j){
            if(c[b[j].x]==i&&c[b[j].y]==i){
                s=b[j].x;
                add(b[j].x,b[j].y,b[j].k,b[j].b);
            }
        }
        if(s) calc(s,i);
    }


    for(reg i=1;i<=scc;++i) f[i][i]=1;
    for(reg i=1;i<=m;++i){
        f[c[b[i].x]][c[b[i].y]]=1;
    }
    for(reg k=1;k<=scc;++k){
        for(reg i=1;i<=scc;++i){
            for(reg j=1;j<=scc;++j){
                f[i][j]|=(f[i][k])&(f[k][j]);
            }
        }
    }
    for(reg i=1;i<=n;++i){
        seg ans;
        for(reg j=1;j<=scc;++j){
            if(f[c[1]][j]&&f[j][c[i]]){
               ans=ans&lim[j]; 
            }
        }
        if(ans.empty()){
            puts("0");
        }
        else if(ans.l==-inf||ans.r==inf){
            puts("-1");
        }else{
            printf("%lld\n",ans.r-ans.l+1);
        }
    }
    return 0;
}   

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
*/

普通二分+判负环

因为整个值域都有单调性。知道不合法往哪里走。

 

区间二分?+找负环

二分左端点,二分右端点。

麻烦的是,第一次不合法,该往哪里走?(显然之后不合法其实是知道往哪里走的)

因为并没有单调性。

本题提供的思路是,考虑不合法的构成,来限制往哪里走才可能合法。

也就是额外记录一些东西

(好像这个套路暂时只出现于k*mid+b的k正负判断?)

 


 

upda:2019.6.21

官方题解:

利用Bellman-ford的思想进行判断负环。

简介Bellman-ford的方法:

不断枚举边数,用所有的边更新点的dis。

设f[i][j]走了i条边,到达j点,最短路

如果存在负环,则一定存在一个负环上的点u,使得f[u][n]<f[u][n-1]

 

扩展一下

f[i][j][k]表示,走了i条边,到达j点,斜率是k的最小的b值。

找到每个点不在负环的x的取值范围。

min(f[u][n][k1]+k1*x)>=min(f[u][n-1][k2]+k2*x)的x的个数。

枚举每个k1,求f[u][n][k1]+k1*x>=min(f[u][n-1][k2]+k2*x)的x区间,再求并。

转化为:求f[u][n][k1]+k1*x<min(f[u][n-1][k2]+k2*x)的x区间,求并,再求补集。

再枚举k2,解出的所有不等式f[u][n][k1]+k1*x<f[u][n-1][k2]+k2*x求交。

由于最后答案补集一定是[-inf,l],[l,r],[r,inf],空集,的一种。所以过程中求交求并不会有奇怪情况。

求并的时候sort一下就好了。

然后floyd,从1到u到x的u的区间求交。

 

虽然不是所有的负环上的点都会考虑到,但是每个负环上至少有一个点可以得到限制,使得没有负环。

最后是求交,所以不影响正确性。

 

posted @ 2019-06-17 14:55  *Miracle*  阅读(431)  评论(0编辑  收藏  举报