【题解】 「JLOI2015」城池攻占 左偏树 LOJ2107

Legend

给定 \(n\) 个结点的一棵内向树,每条向上的边有一个转换器,可以使得经过的数字发生下列两种变化之一:

  • 加上 \(v_i\)
  • 乘以 \(v_i\ (v_i > 0)\)

现在有 \(m\) 个数字从一些点出发向根走,走过转换器就会发生变化。

同时每个点有一个限制 \(h_i\),小于 \(h_i\) 的数字走到这里就不能再移动。特别地,走到了根以后再走会跳出这棵树。

请计算,每一个数字走过了多少条边,以及每一个点停下了多少个数字。

\(1 \le n,m \le 3 \times 10^5\),任何时刻,数字的绝对值都在 \(10^{18}\) 内。

Editorial

考虑一步一步从下往上合并数字集合,对于每一个结点的限制,就直接丢掉不满足的数字(一定是一个前缀)。

由于转换器的操作并不会改变集合数字的相对大小,而且这两个标记是可以像 lazy tag 一样合并的,应该会方便不少。

问题是怎么合并?归并排序吗?

变成 \(O(m^2)\) 了……

堆启发式合并吗?

变成 \(O(m \log^2 m)\) 了……

好吧,其实可以直接用左偏树,并且把左偏树也加上 lazy tag 就可以啦!

复杂度 \(O(m \log m)\)

Code

要注意的地方是要时时刻刻 pushdown 懒标记。

以及调用左偏树一定要调用它的根,不然就会 MLE 很惨。。。

#include <bits/stdc++.h>

#define debug(...) ;//fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
	freopen(#x".in" ,"r" ,stdin);\
	freopen(#x".out" ,"w" ,stdout)
#define LL long long

const int MX = 3e5 + 23;
const LL MOD = 998244353;

LL read(){
	char k = getchar(); LL x = 0 ,flg = 1;
	while(k < '0' || k > '9') flg *= k == '-' ? -1 : 1 ,k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x * flg;
}

int head[MX] ,tot;
struct edge{
	int node ,next;
}h[MX];
void addedge(int u ,int v){
	h[++tot] = (edge){v ,head[u]} ,head[u] = tot;
}

#define lch tr[x].ch[0]
#define rch tr[x].ch[1]
struct LeftTree{
	int dis ,rt ,ch[2];
	LL v ,add ,mul;
	LeftTree(){
		dis = rt = ch[0] = ch[1] = v = 0;
		add = 0 ,mul = 1;
	}
}tr[MX];

int find(int x){return tr[x].rt == x ? x : tr[x].rt = find(tr[x].rt);}

void domul(int x ,LL v){
	tr[x].add *= v;
	tr[x].mul *= v;
	tr[x].v *= v;
}

void doadd(int x ,LL v){
	tr[x].add += v;
	tr[x].v += v;
}

void pushdown(int x){
	if(tr[x].mul != 1){
		if(lch) domul(lch ,tr[x].mul);
		if(rch) domul(rch ,tr[x].mul);
		tr[x].mul = 1;
	}
	if(tr[x].add){
		if(lch) doadd(lch ,tr[x].add);
		if(rch) doadd(rch ,tr[x].add);
		tr[x].add = 0;
	}
}

int merge(int x ,int y){
	debug("Merge %d %d\n" ,x ,y);
	if(!x || !y) return x + y;
	pushdown(x) ,pushdown(y);
	if(tr[x].v > tr[y].v) std::swap(x ,y);
	rch = merge(rch ,y);
	if(tr[lch].dis < tr[rch].dis) std::swap(lch ,rch);
	tr[x].dis = tr[rch].dis + 1;
	tr[lch].rt = tr[rch].rt = tr[x].rt = x;
	return x;
}

void pop(int x){
	pushdown(x);
	tr[x].v = LLONG_MAX;
	tr[lch].rt = lch;
	tr[rch].rt = rch;
	tr[x].rt = merge(lch ,rch);
}

int n ,m;
LL def[MX] ,del[MX];
int type[MX];
int castle[MX] ,knight[MX];

int st[MX] ,dep[MX];
LL a[MX];

int refer[MX];
void dfs(int x){
	for(int i = head[x] ,d ; i ; i = h[i].next){
		dep[d = h[i].node] = dep[x] + 1;
		dfs(d);
		if(refer[x]){
			refer[x] = merge(find(refer[x]) ,find(refer[d]));
		}
		else{
			refer[x] = find(refer[d]);
		}
	}
	while(tr[find(refer[x])].v < def[x]){
		int id = find(refer[x]);
		debug("knight %d die on castle %d!\n" ,id ,x);
		knight[id] = dep[st[id]] - dep[x];
		++castle[x];
		pop(id);
	}
	if(!find(refer[x])) return;
	if(type[x] == 0){
		doadd(find(refer[x]) ,del[x]);
	}
	else{
		domul(find(refer[x]) ,del[x]);
	}
	// debug("v[0] = %lld after dfs %d\n" ,tr[0].v ,x);
}

void solve(){
	tr[0].v = LLONG_MAX;
	tr[0].dis = -1;
	n = read() ,m = read();
	for(int i = 1 ; i <= n ; ++i){
		def[i] = read();
	}
	for(int i = 2 ,f ; i <= n ; ++i){
		f = read() ,type[i] = read() ,del[i] = read();
		addedge(f ,i);
	}
	for(int i = 1 ; i <= m ; ++i){
		a[i] = read() ,st[i] = read();
		tr[i].v = a[i] ,tr[i].rt = i;
		tr[i].ch[0] = tr[i].ch[1] = 0;
		if(!refer[st[i]]){
			refer[st[i]] = i;
		}
		else{
			merge(find(refer[st[i]]) ,find(i));
		}
	}
	dfs(1);
	while(tr[find(refer[1])].v != LLONG_MAX){
		int id = find(refer[1]);
		knight[id] = dep[st[id]] + 1;
		pop(id);
	}
	for(int i = 1 ; i <= n ; ++i){
		printf("%d\n" ,castle[i]);
	}
	for(int j = 1 ; j <= m ; ++j){
		printf("%d\n" ,knight[j]);
	}
}

int main(){
	__FILE([JLOI2015]城池攻占);
	int T = 1;
	for(int i = 1 ; i <= T ; ++i){
		solve();
	}
	return 0;
}
posted @ 2020-10-29 10:55  Imakf  阅读(97)  评论(0编辑  收藏  举报