[LCT动态树] 魔法森林,树点涂色,三叉神经树,历史

A:[NOI2014] 魔法森林

题目

按照 a a a精灵从小到大排序

按顺序插入每一条边

加入第 i i i条边后的最小代价为 a [ i ] a[i] a[i]加上从 1 1 1 n n n的所有路径中最大 b b b最小的路径代价

维护边权 把边在 L C T LCT LCT中理解为点?——看题解代码其实就是建虚点

出现环时 选择最大的 b b b删去即可

/*
一些不成型的想法:

贪心??怎么贪两个呢??——先让其中一维变成有序再着重贪另一维
私以为此题这样不可做 pass

kruskal重构树——最小生成树
最小生成树上的两点间距离最小

LCT维护最小生成树
a精灵从小到大排序 插入b精灵??

边权改成点权 构造虚点
*/
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 150005
#define inf 0x7f7f7f7f 
struct node {
    
    
	int u, v, a, b;
}edge[maxn];
struct LCT {
    
    
	int f, val, rev, id;
	int son[2];
}t[maxn];
int n, m, ans = inf;
int st[maxn], w[maxn];

bool cmp( node x, node y ) {
    
    
	return ( x.a == y.a ) ? ( x.b < y.b ) : ( x.a < y.a );
}

bool isroot( int x ) {
    
    
	return t[t[x].f].son[0] == x || t[t[x].f].son[1] == x;
}

void pushup( int x ) {
    
    
	t[x].id = x, t[x].val = w[x];
	if( t[x].son[0] && t[t[x].son[0]].val > t[x].val ) {
    
    
		t[x].val = t[t[x].son[0]].val;
		t[x].id = t[t[x].son[0]].id;
	}
	if( t[x].son[1] && t[t[x].son[1]].val > t[x].val ) {
    
    
		t[x].val = t[t[x].son[1]].val;
		t[x].id = t[t[x].son[1]].id;
	}
}

void rotate( int x ) {
    
    
	int fa = t[x].f, Gfa = t[fa].f, k = t[fa].son[1] == x;
	if( isroot( fa ) ) t[Gfa].son[t[Gfa].son[1] == fa] = x;
	t[x].f = Gfa;
	if( t[x].son[k ^ 1] ) t[t[x].son[k ^ 1]].f = fa;
	t[fa].son[k] = t[x].son[k ^ 1];
	t[x].son[k ^ 1] = fa;
	t[fa].f = x;
	pushup( fa );
	pushup( x );
}

void pushdown( int x ) {
    
    
	if( t[x].rev ) {
    
    
		swap( t[x].son[0], t[x].son[1] );
		if( t[x].son[0] ) t[t[x].son[0]].rev ^= 1;
		if( t[x].son[1] ) t[t[x].son[1]].rev ^= 1;
		t[x].rev = 0;
	}
}

void splay( int x ) {
    
    
	int top = 0, y = x;
	st[++ top] = y;
	while( isroot( y ) ) st[++ top] = y = t[y].f;
	while( top ) pushdown( st[top --] );
	while( isroot( x ) ) {
    
    
		int fa = t[x].f, Gfa = t[fa].f;
		if( isroot( fa ) ) 
			( ( t[Gfa].son[0] == fa ) ^ ( t[fa].son[0] == x ) ) ? rotate( x ) : rotate( fa );
		rotate( x );
	}
}

void access( int x ) {
    
    
	for( int son = 0;x;son = x, x = t[x].f ) {
    
    
		splay( x );
		t[x].son[1] = son;
		pushup( x );
	}
}

int FindRoot( int x ) {
    
    
	access( x );
	splay( x );
	while( t[x].son[0] ) {
    
    
		pushdown( x );
		x = t[x].son[0];
	}
	return x;
}

void MakeRoot( int x ) {
    
    
	access( x );
	splay( x );
	t[x].rev ^= 1;
	pushdown( x );
}

void link( int x, int y ) {
    
    
	MakeRoot( x );
	if( FindRoot( y ) != x ) t[x].f = y;
}

void split( int x, int y ) {
    
    
	MakeRoot( x );
	access( y );
	splay( y );
}
bool Union( int x, int y ) {
    
    
	MakeRoot( x );
	return FindRoot( y ) == x;
}

int main() {
    
    
	scanf( "%d %d", &n, &m );
	for( int i = 1;i <= m;i ++ )
		scanf( "%d %d %d %d", &edge[i].u, &edge[i].v, &edge[i].a, &edge[i].b );
	sort( edge + 1, edge + m + 1, cmp );
	for( int i = 1;i <= m;i ++ ) {
    
    
		w[i + n] = edge[i].b;
		int u = edge[i].u, v = edge[i].v;
		if( u == v ) continue;
		if( Union( u, v ) ) {
    
    
			split( u, v );
			if( t[v].val <= edge[i].b ) continue;
			int x = t[v].id;
			splay( x );
			t[t[x].son[0]].f = t[t[x].son[1]].f = 0;
			link( u, i + n ), link( i + n, v );
		}
		else
			link( u, i + n ), link( i + n, v );
		if( Union( 1, n ) ) split( 1, n ), ans = min( ans, edge[i].a + t[n].val );
	}
	if( ans == inf ) printf( "-1\n" );
	else printf( "%d\n", ans );
	return 0;
}

D:[ZJOI2018]历史

题目

一句话题意: 给出一棵树,给定每一个点的 a c c e s s access access次数,计算轻重链切换次数的最大值,带修改

先不考虑带修改的问题

u u u,只有在 u u u子树里的点进行 a c c e s s access access才会影响答案,并且点独立,可以分开算

假设 u u u的子树发生了两次 a c c e s s access access,当且仅当这两次操作的点分属于 u u u两个不同儿子的子树中,答案才会 + 1 +1 +1

要使答案最大,就是尽量让所有相邻发生的崛起都来自不同子树

相当于有 [ 0 , m ] [0,m] [0,m]种颜色,每种颜色有 a i a_i ai个,最大化相邻颜色不同的个数

t o t = ∑ i = 0 m a i , m a x x = m a x i = 0 m { a i } tot=\sum_{i=0}^ma_i,maxx=max_{i=0}^m\{a_i\} tot=i=0mai,maxx=maxi=0m{ ai}

答案则为 m i n { t − 1 , 2 × ( t o t − m a x x ) } min\{t-1,2\times (tot-maxx)\} min{ t1,2×(totmaxx)}

考虑先放最多的 m a x x maxx maxx个然后插空,对于剩下的 p o s = t o t − m a x x pos=tot-maxx pos=totmaxx

如果 p o s ≤ m a x x − 1 pos\le maxx-1 posmaxx1,显然直接插空,贡献 2 × p 2\times p 2×p

如果 p o s > m a x x − 1 pos>maxx-1 pos>maxx1,一定可以通过不断来回插空,达到上界 t o t − 1 tot-1 tot1

O ( n ) O(n) O(n)的时间搜一遍整棵树即可

现在加上修改

根据 2 × m a x x ≥ t o t + 1 2\times maxx\ge tot+1 2×maxxtot+1这个分界线处理

为什么这个是分界线??当满足这个式子的时候,答案一定取后者 2 × ( t o t − m a x x ) 2\times (tot-maxx) 2×(totmaxx)

o p t i opt_i opti表示 i i i子树的操作次数和,如果满足 2 × o p t i ≥ o p t f a i + 1 2\times opt_i\ge opt_{fa_i}+1 2×optioptfai+1,就把 i → f a i\rightarrow fa ifa连为实边,否则为虚边

为什么这么定义??因为在这样的定义下,如果 i i i子树内操作 j j j加上权值 w w w,其只会影响 j j j到根路径上的点的答案和虚边关系

因为对于实边,存在 2 ( o p t i + w ) ≥ ( o p t f a i + w ) + 1 2(opt_i+w)\ge (opt_{fa_i}+w)+1 2(opti+w)(optfai+w)+1,即实边还是实边

并且答案 Δ = 2 [ ( o p t f a i + w ) − ( o p t i + w ) ] − 2 [ o p t f a i − o p t i ] = 0 \Delta =2[(opt_{fa_i}+w)-(opt_i+w)]-2[opt_{fa_i}-opt_i]=0 Δ=2[(optfai+w)(opti+w)]2[optfaiopti]=0 是不变的

对于怎么找到路径上的虚边,就写 L C T LCT LCT,因为这里的操作是类似于 a c c e s s access access的,只不过要判断一下虚实边之间的变换

a n s ans ans减去点更新前的答案再加上更新后的答案即可

#include <cstdio>
#include <vector>
using namespace std;
#define int long long
#define maxn 400005
struct node {
    
    
	int fa, val, sum, thin_edge_sum;
	int son[2];
}t[maxn];
vector < int > G[maxn];
int n, m, ans;

bool isroot( int x ) {
    
    
	return t[t[x].fa].son[0] == x || t[t[x].fa].son[1] == x;
}

void pushup( int x ) {
    
    
	t[x].sum = t[x].val + t[t[x].son[0]].sum + t[t[x].son[1]].sum + t[x].thin_edge_sum;
} 

void rotate( int x ) {
    
    
	int fa = t[x].fa, Gfa = t[fa].fa, k = t[fa].son[1] == x;
	if( isroot( fa ) ) t[Gfa].son[t[Gfa].son[1] == fa] = x;
	t[x].fa = Gfa;
	if( t[x].son[k ^ 1] ) t[t[x].son[k ^ 1]].fa = fa;
	t[fa].son[k] = t[x].son[k ^ 1];
	t[x].son[k ^ 1] = fa;
	t[fa].fa = x;
	pushup( fa );
	pushup( x );
}

void splay( int x ) {
    
    
	while( isroot( x ) ) {
    
    
		int fa = t[x].fa, Gfa = t[fa].fa;
		if( isroot( fa ) )
			( ( t[Gfa].son[1] == fa ) ^ ( t[fa].son[1] == x ) ) ? rotate( fa ): rotate( x );
		rotate( x );
	}
}

int calc( int x, int tot, int maxx ) {
    
    
	if( t[x].son[1] ) return ( tot - maxx ) * 2;
	else if( t[x].val * 2 > tot ) return ( tot - t[x].val ) * 2;
	else return tot - 1;
}

void modify( int x, int w ) {
    
    
	splay( x );
    /*
    	如果想要得到某一个点的tot
    	就应该计算splay里面深度≥它的点的val之和
    	也就是将这个点splay到根后
    	这个点的val加上其右子树的val和
    */
	int tot = t[x].sum - t[t[x].son[0]].sum;
	int maxx = t[t[x].son[1]].sum;
	ans -= calc( x, tot, maxx );
	t[x].val += w, tot += w, t[x].sum += w;
	if( maxx * 2 < tot + 1 ) {
    
     //实边变虚边
		t[x].thin_edge_sum += maxx;
		t[x].son[1] = 0;
	}
	ans += calc( x, tot, maxx );
	pushup( x );
	int son;
	for( x = t[son = x].fa;x;x = t[son = x].fa ) {
    
    
		splay( x );
		int tot = t[x].sum - t[t[x].son[0]].sum;
		int maxx = t[t[x].son[1]].sum;
		ans -= calc( x, tot, maxx );
		t[x].thin_edge_sum += w, tot += w, t[x].sum += w;
		if( maxx * 2 < tot + 1 ) {
    
    
			t[x].thin_edge_sum += maxx;
			t[x].son[1] = 0;
			maxx = 0;
		}
		if( t[son].sum * 2 > tot ) {
    
    
			t[x].thin_edge_sum -= t[son].sum;
			t[x].son[1] = son;
			maxx = t[son].sum;
		}
		ans += calc( x, tot, maxx );
		pushup( x );
	}
}

void dfs( int u ) {
    
    
	t[u].sum = t[u].val;
	int pos = 0, maxx = t[u].val;
	for( int i = 0;i < G[u].size();i ++ ) {
    
    
		int v = G[u][i];
		if( v == t[u].fa ) continue;
		t[v].fa = u;
		dfs( v );
		t[u].sum += t[v].sum;
		if( t[v].sum > maxx ) maxx = t[v].sum, pos = v;
	}
	ans += min( t[u].sum - 1, ( t[u].sum - maxx ) * 2 );
	if( maxx * 2 >= t[u].sum + 1 ) t[u].son[1] = pos; //实边 修改是不会更改贡献的
	t[u].thin_edge_sum = t[u].sum - t[u].val - t[t[u].son[1]].sum;//所有虚边的操作数和 以后是会更改贡献的
}

signed main() {
    
    
	scanf( "%lld %lld", &n, &m );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld", &t[i].val );
	for( int i = 1, u, v;i < n;i ++ ) {
    
    
		scanf( "%lld %lld", &u, &v );
		G[u].push_back( v );
		G[v].push_back( u );  
	}
	dfs( 1 );
	printf( "%lld\n", ans );
	for( int i = 1, x, y;i <= m;i ++ ) {
    
    
		scanf( "%lld %lld", &x, &y );
		modify( x, y );
		printf( "%lld\n", ans );
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Emm_Titan/article/details/114952267