[BZOJ2788]-[Poi2012]Festival-差分约束+tarjan+floyd

本文探讨了如何通过差分约束系统解决一个特定问题:在满足一系列限制条件下,求一组正整数最大可能的不同值数量。文章详细介绍了建图过程,并对比了最短路与最长路的不同含义。

说在前面

已经三个月没碰过差分约束题了…花了一晚上时间,才把最长路和最短路建图的含义搞清楚
这道题是把算法强行糅在一起的,写起来比较简单,1A了
然而me居然花了10284ms才过!!!?各种优化之后仍然9832ms???

翻rank榜的时候,发现LKQ的这道题只跑了1800ms,以为他的代码肯定有「过人之处」,于是去学习了一下他的代码
发现他是建最长路图的,而me是建的最短路图…黑人问号脸.jpg
去把popoQQQ和wzq_qwq的代码分别交了一遍,popoQQQ的最短路15512ms,跑得比me还慢,而wzq_qwq的最长路tm居然只有1052ms
「过人之处」?? 噫噫噫!!!


题目

BZOJ2788传送门

题面

有n个正整数X1,X2,…,Xn,再给出m1+m2个限制条件,限制分为两类:
1. 给出a,b (1<=a,b<=n),要求满足Xa + 1 = Xb
2. 给出c,d (1<=c,d<=n),要求满足Xc <= Xd
在满足所有限制的条件下,求集合{Xi}大小的最大值。

输入输出格式

输入格式:
第一行三个正整数n, m1, m2 (2<=n<=600, 1<=m1+m2<=100,000)。
接下来m1行每行两个正整数a,b (1<=a,b<=n),表示第一类限制。
接下来m2行每行两个正整数c,d (1<=c,d<=n),表示第二类限制。

输出格式:
一个正整数,表示集合{Xi}大小的最大值。
如果无解输出NIE。


解法

首先很明显的差分约束系统建图,关于最长路和最短路的差别在代码的注释里

还是手动模拟几组小数据,可以发现,同一个强连通分量里点的相对大小是被固定了的,且边权只有0,-1,1,所以同一个强连通分量里出现的数字个数应该是max{任意两点最短路}+1,也就是从距离最小的点开始+1+1+1…+1到距离最长的点

而不同强连通分量的连接,只会是「大于等于」所建的边,那么两个分量是可以做到互不影响的(一个值为无限小而另一个为无限大)。因此,最后的答案就是所有的max{最短路}+1之和


下面是自带大常数的代码

实现的时候可以不用专门写个SPFA判环
可以在最后判一下如果mp[i][i]<0,就是NIE

//由题目中的条件可以差分约束系统建图
//建图跑的是最短路,表示在确定了一个数字的情况下,符合约束的最大值
//如果跑最长路的话,就变成了在确定一个数字的情况下,求解符合约束的最小值,
//等式在两种情况下建边,虽然样子一样,但是+1边一个是由大于等于得来的,一个是由小于等于得来的
//所以两种含义都是正确的,最长路情况下需要判正权环,而最短路判的是负权环
//
//最短路情况下,答案等于tarjan后所有最短路<最长+1>的和,表示的是当前答案固定,最大的答案比他大多少
//最长路情况下,答案等于tarjan后所有最长路<绝对值最长+1>的和,表示的是当前答案固定,最小的答案比他小多少
#include <cstdio> 
#include <cstring>
#include <algorithm>
using namespace std ;

int N , M1 , M2 , head[605] , tp , mp[605][605] ;
struct Path{
    int pre , to , len ;
}p[200005] ;

inline void In( int t1 , int t2 , int t3 ){
    p[++tp].pre = head[t1] ;
    p[ head[t1] = tp ].to = t2 ;
    p[tp].len = t3 ;
}

bool inque[605] ;
int dis[605] , que[720005] , fr , ba , len[605] ;
bool SPFA(){
    fr = 360001 , ba = 360000 ;
    for( int i = 1 ; i <= N ; i ++ )
        que[++ba] = i , inque[i] = true ;
    while( fr <= ba ){
        int u = que[fr++] ; inque[u] = false ;
        for( int i = head[u] ; i ; i = p[i].pre ){
            int v = p[i].to ;
            if( dis[v] > dis[u] + p[i].len ){
                dis[v] = dis[u] + p[i].len ;
                len[v] = len[u] + 1 ;
                if( len[v] > N ) return false ;
                if( !inque[v] ){
                    if( dis[v] < dis[ que[fr] ]) que[--fr] = v ;
                    else que[++ba] = v ;
                    inque[v] = true ;
                }
            }
        }
    }
    return true ;
}

int b[605] , cnt , ans ;
int calc(){
    for( int k = 1 ; k <= cnt ; k ++ )
        for( int i = 1 ; i <= cnt ; i ++ )
            for( int j = 1 ; j <= cnt ; j ++ )
                mp[b[i]][b[j]] = min( mp[b[i]][b[j]] ,
                mp[b[i]][b[k]] + mp[b[k]][b[j]] ) ;
    int rt = 0 ;
    for( int i = 1 ; i <= cnt ; i ++ )
        for( int j = 1 ; j <= cnt ; j ++ )
            rt = max( rt , mp[b[i]][b[j]] ) ;
    return rt + 1 ;
}

int dfn[605] , dfs_c , sta[605] , topp , scc[605] , scccnt , low[605] ; 
void dfs( int u ){
    low[u] = dfn[u] = ++dfs_c ;
    sta[++topp] = u ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( !dfn[v] ){
            dfs( v ) ;
            low[u] = min( low[u] , low[v] ) ;
        }
        else if( !scc[v] )
            low[u] = min( low[u] , dfn[v] ) ;
    }
    if( low[u] == dfn[u] ){
        scccnt ++ ;
        cnt = 0 ;
        while( 1 ){
            int x = sta[topp--] ;
            scc[x] = scccnt ;
            b[++cnt] = x ;
            if( x == u ) break ;
        }
        ans += calc() ;
    }
}

void solve(){
    if( !SPFA() ){
        puts( "NIE" ) ;
        return ;
    }
    for( int i = 1 ; i <= N ; i ++ )
        if( !dfn[i] ) dfs( i ) ;
    printf( "%d" , ans ) ;
}

inline int read_(){
    int rt = 0 ;
    char ch = getchar() ;
    while( ch < '0' || ch > '9' ) ch = getchar() ;
    while( ch >='0' && ch <='9' ) rt = (rt<<1) + (rt<<3) + ch - '0' , ch = getchar() ;
    return rt ;
}

int main(){
    scanf( "%d%d%d" , &N , &M1 , &M2 ) ;
    for( int i = 1 ; i <= N ; i ++ )
        for( int j = 1 ; j <= N ; j ++ )
            mp[i][j] = ( i == j ? 0 : 0x3f3f3f3f ) ;
    for( int i = 1 , a , b ; i <= M1 ; i ++ ){
        a = read_() , b = read_() ;
        In( a , b , 1 ) ; mp[a][b] = min( mp[a][b] , 1 ) ;
        In( b , a , -1 ) ;mp[b][a] = min( mp[b][a] , -1 ) ;
    }
    for( int i = 1 , a , b ; i <= M2 ; i ++ ){
        a = read_() , b = read_() ;
        In( b , a , 0 ) ; mp[b][a] = min( mp[b][a] , 0 ) ;
    }
    solve() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值