现在的位置: 首页 > 综合 > 正文

【BZOJ】2001 [Hnoi2010]City 城市建设 cdq分治——动态最小生成树

2017年10月15日 ⁄ 综合 ⁄ 共 3213字 ⁄ 字号 评论关闭

传送门:【BZOJ】2001  [Hnoi2010]City 城市建设


题目分析:今天一中午+一下午的时间全部交代给这题了。。。

两个关键的操作:
Reduction(删除无用边):
把待修改的边标为INF,做一遍MST,把做完后不在MST中的非INF边删去(因为这些边在原图的情况下肯定更不可能选进MST的边集,即无用边);
Contraction(缩必须边,缩点):
把待修改的边标为-INF,做一遍MST,在MST中的非-INF边为必须边(因为这些边在原图的情况下也一定会被选进MST),缩点。
cdq_solve ( l , r ):
    if ( l == r ) {
        求mst,记录结果;
        return ;
    }
    Contraction&Reduction;
    solve ( l , m ) ;
    solve ( m + 1 , r ) ;
end ;
这样不断处理并递归下去到最后一层点数以及边数都很少了。
大概复杂度:T(n) = T(n/2) + O(nlogn) ==> T(n) = O(nlog^2n)

代码如下:

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std ;
 
#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 )
#define ls ( o << 1 )
#define rs ( o << 1 | 1 )
#define lson ls , l , m
#define rson rs , m + 1 , r
#define root 1 , 1 , cnt
#define mid ( ( l + r ) >> 1 )

typedef long long LL ;

const int MAXN = 20005 ;
const int MAXM = 50005 ;
const int MAXQ = 50005 ;
const int INF = 0x3f3f3f3f ;

struct Edge {
	int u , v , d ;
	int idx ;
	bool operator < ( const Edge& a ) const {
		return d < a.d ;
	}
} E[17][MAXM] , L[MAXM] , tmp[MAXM] ;//E[][]层次图,L为当前层次的图,tmp为中间变量

struct ASK {
	int k , d ;
} ask[MAXQ] ;//询问

int edge_num[17] , node_num[17] ;//每一层点和边的数量
bool change[MAXM] ;//change[] = 1则该边是必须在最小生成树内的边
int newv[MAXN] ;//点的新标号
int real[MAXM] ;//将边的编号转化为改变后的编号
int p[MAXN] , rank[MAXN] ;//并查集专用
int val[MAXM] ;//边的价值
LL ans[MAXQ] ;//答案
int n , m , q ;

int find ( int x ) {
	int o = x , ans , tmp ;
	while ( o != p[o] ) o = p[o] ;
	ans = o , o = x ;
	while ( o != p[o] ) {
		tmp = p[o] ;
		p[o] = ans ;
		o = tmp ;
	}
	return ans ;
}

bool merge ( int x , int y ) {//合并return 1,否则return 0
	x = find ( x ) ;
	y = find ( y ) ;
	if ( x == y ) return 0 ;
	if ( rank[x] <= rank[y] ) {
		p[x] = y ;
		if ( rank[x] == rank[y] ) ++ rank[y] ;
	} else p[y] = x ;
	return 1 ;
}

void clear ( int n ) {
	FOR ( i , 1 , n ) p[i] = i , rank[i] = 0 ;
}

void contraction ( int& NV , int& NE , LL& res ) {
	int NV_2 = 0 , NE_2 = 0 ;
	clear ( NV ) ;
	REP ( i , 0 , NE ) change[i] = 0 ;
	sort ( L , L + NE ) ;
	REP ( i , 0 , NE ) {
		if ( merge ( L[i].u , L[i].v ) && L[i].d != -INF ) {//必须在树中的边
			res += L[i].d ;
			change[i] = 1 ;
		} else tmp[NE_2 ++] = L[i] ;
	}
	clear ( NV ) ;
	REP ( i , 0 , NE ) if ( change[i] ) merge ( L[i].u , L[i].v ) ;
	FOR ( i , 1 , NV ) if ( find ( i ) == i ) newv[i] = ++ NV_2 ;//点重标号
	FOR ( i , 1 , NV ) newv[i] = newv[find ( i )] ;
	REP ( i , 0 , NE_2 ) {
		L[i] = tmp[i] ;
		real[L[i].idx] = i ;//修改边的实际对应编号
		L[i].u = newv[L[i].u] ;
		L[i].v = newv[L[i].v] ;
	}
	NV = NV_2 ;
	NE = NE_2 ;
}

void reduction ( int& NV , int& NE ) {
	int NE_2 = 0 ;
	clear ( NV ) ;
	sort ( L , L + NE ) ;
	REP ( i , 0 , NE ) if ( merge ( L[i].u , L[i].v ) || L[i].d == INF ) {//保存可以变成树边的边
		real[L[i].idx] = NE_2 ;//修改边的实际对应编号
		L[NE_2 ++] = L[i] ;
	}
	NE = NE_2 ;
}

void cdq_solve ( int l , int r , int cur , LL res ) {
	int NV = node_num[cur] ;//当前层次图的点数
	int NE = edge_num[cur] ;//当前层次图的边数
	if ( l == r ) val[ask[l].k] = ask[l].d ;//修改边权
	REP ( i , 0 , NE ) {
		E[cur][i].d = val[E[cur][i].idx] ;
		L[i] = E[cur][i] ;
		real[L[i].idx] = i ;
	}
	if ( l == r ) {//只有一个查询操作,直接MST求解
		clear ( NV ) ;
		sort ( L , L + NE ) ;
		REP ( i , 0 , NE ) if ( merge ( L[i].u , L[i].v ) ) res += L[i].d ;
		ans[l] = res ;
		return ;
	}
	FOR ( i , l , r ) L[real[ask[i].k]].d = -INF ;
	contraction ( NV , NE , res ) ;//将必须在最小生成树内的边缩去
	FOR ( i , l , r ) L[real[ask[i].k]].d =  INF ;
	reduction ( NV , NE ) ;//将一定不在最小生成树内的边删除
	node_num[cur + 1] = NV ;//保存下一层次图的点数
	edge_num[cur + 1] = NE ;//保存下一层次图的边数
	REP ( i , 0 , NE ) E[cur + 1][i] = L[i] ;//建立下一层次图
	int m = ( l + r ) >> 1 ;
	cdq_solve ( l , m , cur + 1 , res ) ;
	cdq_solve ( m + 1 , r , cur + 1 , res ) ;
}

void solve () {
	REP ( i , 0 , m ) {
		scanf ( "%d%d%d" , &E[0][i].u , &E[0][i].v,  &E[0][i].d ) ;
		val[i] = E[0][i].d ;
		E[0][i].idx = i ;
	}
	FOR ( i , 1 , q ) {
		scanf ( "%d%d" , &ask[i].k , &ask[i].d ) ;
		-- ask[i].k ;
	}
	node_num[0] = n ;
	edge_num[0] = m ;
	cdq_solve ( 1 , q , 0 , 0 ) ;
	FOR ( i , 1 , q ) printf ( "%lld\n" , ans[i] ) ;
}

int main () {
	scanf ( "%d%d%d" , &n , &m , &q ) ;
	solve () ;
	return 0 ;
}

抱歉!评论已关闭.