【ACdream】1223 Roads Andrew Stankevich Contest 2 KM匹配

传送门:【ACdream】1223 Roads Andrew Stankevich Contest 2


题目分析:KM果题啊。。。。

这题就是考察KM匹配的性质:KM匹配中任何时刻满足dx[i]+dy[j]>=w[i][j],其中w[i][j]是边(i,j)的权值,dx,dy是可行顶标的权值。而且最大权匹配结束以后一定是完备匹配且所有顶标之和一定最小。

题目要求保留所有的石头路,而所有的石头路构成一棵树。首先dfs一遍石头路建树,然后每添加一条编号为j的普通路时一定会和石头路成环,那么这个环上编号为i的石头路和其的关系为修改边权以后满足:c[i]-d[i]<=c[j]+d[j]。其中c[i]为路初始的权值,d[i]为路改变权值的幅度。为什么一定是石头路减而普通路加?想想也明白石头路加权值或者普通路减权值不过是白费力气,还可能增大花费。

那么我们对c[i]-d[i]<=c[j]+d[j]稍作变形可得d[i]+d[j]>=c[i]-c[j],可以发现右边的c[i]和c[j]都是给定的,是常数!设w[i][j] = c[i] - c[j],则d[i]+d[j]>=c[i]-c[j] => d[i]+d[j]>=w[i][j]。这不正是KM匹配的基本性质吗?于是我们求一次最大权匹配自然就得到了满足题目要求的顶标。

对于KM算法我并不了解,昨天因为这道题才开始看。。以前都是用费用流写KM的。。。这次因为普通的费用流不维护顶标,所以才打算看掉KM的,翻翻白书才感到很蛋疼,竟然发现dx[i]+dy[j]>=w[i][j]竟然就是KM的基本性质= =。接下来应该学学原始-对偶了,不知道能不能找到好点的资料。


不知道KM算法还有什么地方能优化的,先放放。。


代码如下:


#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std ;
 
typedef long long LL ;

#pragma comment(linker, "/STACK:16777216")
#define rep( i , a , b ) for ( int i = a ; i < b ; ++ i )
#define For( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define rev( i , a , b ) for ( int i = a ; i >= b ; -- i )
#define travel( e , H , u ) for ( Edge* e = H[u] ; e ; e = e -> next )
#define clr( a , x ) memset ( a , x , sizeof a )
#define cpy( a , x ) memcpy ( a , x , sizeof a )

const int MAXN = 405 ;
const int MAXE = 233 ;
const int INF = 0x3f3f3f3f ;

struct Edge {
	int v , c , idx ;
	Edge* next ;
} E[MAXE] , *H[MAXN] , *edge , *idx[MAXN] ;

int dep[MAXN] ;
int pre[MAXN] ;
int G[MAXN][MAXN] ;
int visx[MAXN] , visy[MAXN] , Time ;
int Lx[MAXN] , Ly[MAXN] ;
int dx[MAXN] , dy[MAXN] ;
int slack[MAXN] ;
int cost[MAXN] ;
int n , m , K , V ;

void clear () {
	edge = E ;
	clr ( H , 0 ) ;
	dep[1] = pre[1] = 0 ;
}

void addedge ( int u , int v , int c , int idx ) {
	edge->v = v ;
	edge->c = c ;
	edge->idx = idx ;
	edge->next = H[u] ;
	H[u] = edge ++ ;
}

int dfs ( int u ) {
	travel ( e , H , u ) {
		int v = e->v ;
		if ( v == pre[u] ) continue ;
		dep[v] = dep[u] + 1 ;
		pre[v] = u ;
		idx[v] = e ;
		dfs ( v ) ;
	}
}

void process ( int x , int y , int cv , int v ) {
	while ( x != y ) {
		if ( dep[x] < dep[y] ) swap ( x , y ) ;
		int u = idx[x]->idx , cu = idx[x]->c ;
		if ( cv < cu ) G[u][v] = cu - cv ;
		x = pre[x] ;
	}
}

int find ( int u ) {
	visx[u] = Time ;
	For ( v , 1 , m ) {
		if ( visy[v] == Time ) continue ;
		int tmp = dx[u] + dy[v] - G[u][v] ;
		if ( !tmp ) {
			visy[v] = Time ;
			if ( Ly[v] == -1 || find ( Ly[v] ) ) {
				Lx[u] = v ;
				Ly[v] = u ;
				return 1 ;
			}
		} else if ( slack[v] > tmp ) slack[v] = tmp ;
	}
	return 0 ;
}

void KM () {
	clr ( Lx , -1 ) ;
	clr ( Ly , -1 ) ;
	clr ( dx , -INF ) ;
	clr ( dy , 0 ) ;
	rep ( i , 1 , n ) For ( j , 1 , m ) if ( G[i][j] > dx[i] ) dx[i] = G[i][j] ;
	rep ( i , 1 , n ) {
		For ( j , 1 , m ) slack[j] = INF ;
		while ( 1 ) {
			++ Time ;
			if ( find ( i ) ) break ;
			int d = INF ;
			For ( j , 1 , m ) if ( visy[j] != Time && d > slack[j] ) d = slack[j] ;
			rep ( j , 1 , n ) if ( visx[j] == Time ) dx[j] -= d ;
			For ( j , 1 , m ) visy[j] == Time ? dy[j] += d : slack[j] -= d ;
		}
	}
}

void solve () {
	int u , v , c ;
	clear () ;
	clr ( G , 0 ) ;
	m = max ( n - 1 , K - ( n - 1 ) ) ;
	rep ( i , 1 , n ) {
		scanf ( "%d%d%d" , &u , &v , &c ) ;
		addedge ( u , v , c , i ) ;
		addedge ( v , u , c , i ) ;
		cost[i] = c ;
	}
	dfs ( 1 ) ;
	For ( i , n , K ) {
		scanf ( "%d%d%d" , &u , &v , &c ) ;
		process ( u , v , c , i - ( n - 1 ) ) ;
		cost[i] = c ;
	}
	KM () ;
	For ( i , 1 , K ) printf ( "%d\n" , i < n ? cost[i] - dx[i] : cost[i] + dy[i - ( n - 1 )] ) ;
}

int main () {
	clr ( visx , 0 ) ;
	clr ( visy , 0 ) ;
	Time = 0 ;
	while ( ~scanf ( "%d%d" , &n , &K ) ) solve () ;
	return 0 ;
}


你可能感兴趣的:(ACdream)