单纯形的时间复杂度,简直不可理喻
BZOJ3118传送门
给出一个N个点M条边的无向图,边有边权
现在me在这个图上,钦定一棵最小生成树,当然这个边权可能是不符合的
所以现在有两种操作:
给第 i i 条边边权 +1 + 1 ,花费 Ai A i
给第 i i 条边边权 −1 − 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 A i , B i 如题
输出格式:
输出一个整数表示答案
还是想说这个复杂度= =
一条非树边,满足这条边与MST所形成的环中,这条边的权值最大
所以我们就让每条非树边都达成这个成就
对于修改操作,可以发现一定是这样的:树边只会减少,非树边只会增加
由此可以列出一个线性规划式
但是发现它并没有初始解(因为形如 xi+xj≥lenj−leni x i + x j ≥ l e n j − l e n i , j j 是树边)
不过呢,它的最优化条件是这样: ∑feeixi ∑ f e e i x i 求最小值
所以它对偶一下之后,就直接是个标准型了,直接上单纯形法就可以了
附:关于整数规划的问题
这个题是一个整数规划,而单纯形法是用于实数规划的
不过呢,根据VFleaKing和hzhwcmhf两位前辈在贴吧上提到的内容,如果系数矩阵(就是 aij a i j )是个全幺模矩阵(定义:全幺模矩阵的任何子方阵的行列式等于1,-1或0),那么就一定存在一组整数解,它与实数最优解一样优秀。VKF那个帖子的传送门。另外,16年的论文的第89页里也有简单提到
而这个题在对偶之后,显然可以得到这是一个全幺模矩阵,于是直接单纯形就好了
那么如何判定全幺模矩阵呢?
这个矩阵必须同时满足(充分不必要):
或者:这个题可以用网络流做(当然这个条件没什么用)
另外hzhwcmhf提到,如果有一种列的排列能使得每行的1是连续的,那么也是全幺模的
另外的性质:
一个矩阵的转置是全幺模矩阵的话,那么它自己也是全幺模
如果行与行,列与列交换之后是全幺模,那么它自己也是全幺模
另外,这道题原形是BZOJ1937: [Shoi2004]Mst 最小生成树,不过这道题空间只有64M
不不不,我们只需要适当的猜测一下,大概有多少限制就可以了(3118这道题据说保证了限制不多于超过4000?)
实测把数组开到a[770][9900]可过(就是770条边,每条边一个方程的情况下,最多容许9900条限制)
美滋滋~就是这么玄学~
#include
#include
#include
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() ;
}