[BZOJ3118]-Orz the MST-单纯形

说在前面

单纯形的时间复杂度,简直不可理喻


题目

BZOJ3118传送门

题目大意

给出一个N个点M条边的无向图,边有边权
现在me在这个图上,钦定一棵最小生成树,当然这个边权可能是不符合的
所以现在有两种操作:
给第 i i 条边边权+1,花费 Ai A i
给第 i i 条边边权1,花费 Bi B i
Ai,Bi A i , B i 可能不同
现在寻求一种花费最小的方式,使得me钦定的那棵树成为最小生成树

点数 300 ≤ 300 ,其余所有数字 1000 ≤ 1000

输入输出格式

输入格式:
第一行两个整数N和M,含义如题
接下来M行,每行六个整数 u,v,len,FFi,Ai,Bi u , v , l e n , F F i , A i , B i
其中, u,v u , v 表示这条边的端点, len l e n 表示边权, FFi F F i 表示是否在树中,为 1 1 则在,Ai,Bi如题

输出格式:
输出一个整数表示答案


解法

还是想说这个复杂度= =
一条非树边,满足这条边与MST所形成的环中,这条边的权值最大
所以我们就让每条非树边都达成这个成就

对于修改操作,可以发现一定是这样的:树边只会减少,非树边只会增加
由此可以列出一个线性规划式

但是发现它并没有初始解(因为形如 xi+xjlenjleni x i + x j ≥ l e n j − l e n i j j 是树边)
不过呢,它的最优化条件是这样:feeixi求最小值
所以它对偶一下之后,就直接是个标准型了,直接上单纯形法就可以了


附:关于整数规划的问题
这个题是一个整数规划,而单纯形法是用于实数规划的
不过呢,根据VFleaKing和hzhwcmhf两位前辈在贴吧上提到的内容,如果系数矩阵(就是 aij a i j )是个全幺模矩阵(定义:全幺模矩阵的任何子方阵的行列式等于1,-1或0),那么就一定存在一组整数解,它与实数最优解一样优秀。VKF那个帖子的传送门。另外,16年的论文的第89页里也有简单提到
而这个题在对偶之后,显然可以得到这是一个全幺模矩阵,于是直接单纯形就好了

那么如何判定全幺模矩阵呢?
这个矩阵必须同时满足(充分不必要):

  1. 所有数字全是 0,1,1 0 , 1 , − 1 中的一个
  2. 将矩阵的行划分为两个不相交集合 N,M满足:
    • 每列最多2非0元素
    • 若1列上有2个异号元素 那么它们分别属于N M
    • 若1列上有2个同号元素 那么它们同时属于N 或 M

或者:这个题可以用网络流做(当然这个条件没什么用)
另外hzhwcmhf提到,如果有一种列的排列能使得每行的1是连续的,那么也是全幺模的

另外的性质:
一个矩阵的转置是全幺模矩阵的话,那么它自己也是全幺模
如果行与行,列与列交换之后是全幺模,那么它自己也是全幺模


下面是玄学代码

另外,这道题原形是BZOJ1937: [Shoi2004]Mst 最小生成树,不过这道题空间只有64M
不不不,我们只需要适当的猜测一下,大概有多少限制就可以了(3118这道题据说保证了限制不多于超过4000?)
实测把数组开到a[770][9900]可过(就是770条边,每条边一个方程的情况下,最多容许9900条限制)
美滋滋~就是这么玄学~

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

int N , M , cnt_restriction , tp , head[305] ;
double a[1002][30002] , eps = 1e-8 ;

struct Path{
    int pre , to , id ;
} p[605] ;
struct Edge{
    int fr , to , len , A , B , intree ;
    void read_(){
        scanf( "%d%d%d%d%d%d" , &fr , &to , &len , &intree , &A , &B ) ;
    }
} e[1005] ;

int dcmp( double x ){ return x < -eps ? -1 : x > eps ; }

void In( int t1 , int t2 , int id ){
    p[++tp] = ( Path ){ head[t1] , t2 , id } ; head[t1] = tp ;
    p[++tp] = ( Path ){ head[t2] , t1 , id } ; head[t2] = tp ;
}

int fa[305] , pid[305] , dep[305] ;
void dfs( int u , int f ){
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v == f ) continue ;
        dep[v] = dep[u] + 1 ;
        pid[v] = p[i].id ;
        fa[v] = u , dfs( v , u ) ;
    }
}

void Build( int u , int v , int id ){
    int tu = u , tv = v , Lca ;
    while( u != v ){
        if( dep[u] > dep[v] ) u = fa[u] ;
        else v = fa[v] ;
    } Lca = u , u = tu , v = tv ;
//  printf( "now (%d %d) Lca(%d)\n" , tu , tv , Lca ) ;
    while( u != Lca ){
        cnt_restriction ++ ;
        a[0][cnt_restriction] = e[ pid[u] ].len - e[id].len ;
        a[id][cnt_restriction] = a[ pid[u] ][cnt_restriction] = - 1 ;
        u = fa[u] ;
    }
    while( v != Lca ){
        cnt_restriction ++ ;
        a[0][cnt_restriction] = e[ pid[v] ].len - e[id].len ;
        a[id][cnt_restriction] = a[ pid[v] ][cnt_restriction] = - 1 ;
        v = fa[v] ;
    }
}

void preWork(){
    dfs( 1 , 1 ) ;// puts( "emmm?" ) ;
    for( int i = 1 ; i <= M ; i ++ )
        if( !e[i].intree ){
            Build( e[i].fr , e[i].to , i ) ;
            a[i][0] = e[i].A ;
        } else a[i][0] = e[i].B ;
}

void pivot( int r , int c ){ // here , r is Basis while c is not ;
    double tmp = - a[r][c] ; a[r][c] = -1 ;
    for( int i = 0 ; i <= cnt_restriction ; i ++ ) a[r][i] /= tmp ;
    for( int i = 0 ; i <= M ; i ++ ){
        if( dcmp( a[i][c] == 0 ) || i == r ) continue ;
        tmp = a[i][c] , a[i][c] = 0 ;
        for( int j = 0 ; j <= cnt_restriction ; j ++ )
            a[i][j] += tmp * a[r][j] ;
    }
}

void solve(){
//  printf( "enter !\n" ) ;
    while( true ){
        int x = 0 , y = 0 ;
        double rem = 1e10 ;
        for( int i = 1 ; i <= cnt_restriction && !x ; i ++ )
            if( a[0][i] > eps ) x = i ;
        if( !x ) break ;
        for( int i = 1 ; i <= M ; i ++ )
            if( a[i][x] <-eps && rem > - a[i][0] / a[i][x] )
                rem = - a[i][0] / a[i][x] , y = i ;
        pivot( y , x ) ;
    } printf( "%.0f" , a[0][0] ) ;
}

int main(){
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 ; i <= M ; i ++ ){
        e[i].read_() ;
        if( e[i].intree ) In( e[i].fr , e[i].to , i ) ;
    } preWork() ; solve() ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值