传送门:【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 ; }