P3261 JLOI2015 城池攻占

题目描述

小铭铭最近获得了一副新的桌游,游戏中需要用 \(m\) 个骑士攻占 \(n\) 个城池。

\(n\) 个城池用 \(1\)\(n\) 的整数表示。除 \(1\) 号城池外,城池 \(i\) 会受到另一座城池 \(f_i\) 的管辖,其中 \(f_i<i\)。也就是说,所有城池构成了一棵有根树。

\(m\) 个骑士用 \(1\)\(m\) 的整数表示,其中第 \(i\) 个骑士的初始战斗力为 \(s_i\),第一个攻击的城池为 \(c_i\)

每个城池有一个防御值 \(h_i\),如果一个骑士的战斗力大于等于城池的生命值,那么骑士就可以占领这座城池;否则占领失败,骑士将在这座城池牺牲。占领一个城池以后,骑士的战斗力将发生变化,然后继续攻击管辖这座城池的城池,直到占领 \(1\) 号城池,或牺牲为止。

\(1\) 号城池外,每个城池 \(i\) 会给出一个战斗力变化参数 \((a_i,v_i)\)。若 \(a_i=0\),攻占城池 \(i\) 以后骑士战斗力会增加 \(v_i\);若 \(a_i=1\),攻占城池 \(i\) 以后,战斗力会乘以 \(v_i\)

注意每个骑士是单独计算的。也就是说一个骑士攻击一座城池,不管结果如何,均不会影响其他骑士攻击这座城池的结果。

现在的问题是,对于每个城池,输出有多少个骑士在这里牺牲;对于每个骑士,输出他攻占的城池数量。

输入格式

\(1\) 行包含两个正整数 \(n,m\),表示城池的数量和骑士的数量。

\(2\) 行包含 \(n\) 个整数,其中第 \(i\) 个数为 \(h_i\),表示城池 \(i\) 的防御值。

\(3\sim n+1\) 行,每行包含三个整数。其中第 \(i+1\) 行的三个数为 \(f_i,a_i,v_i\),分别表示管辖这座城池的城池编号和两个战斗力变化参数。

\(n+2\sim n+m+1\) 行,每行包含两个整数。其中第 \(n+i\) 行的两个数为 \(s_i,c_i\),分别表示初始战斗力和第一个攻击的城池。

输出格式

输出 \(n+m\) 行,每行包含一个非负整数。其中前 \(n\) 行分别表示在城池 \(1\)\(n\) 牺牲的骑士数量,后 \(m\) 行分别表示骑士 \(1\)\(m\) 攻占的城池数量。

样例 #1

样例输入 #1

5 5
50 20 10 10 30
1 1 2
2 0 5
2 0 -10
1 0 10
20 2
10 3
40 4
20 4
35 5

样例输出 #1

2
2
0
0
0
1
1
3
1
1

提示

对于 \(100\%\) 的数据,\(1\le n,m\le 3\times 10^5\)\(-10^{18}\le h_i,v_i,s_i\le 10^{18}\)\(1\le f_i<i,1\le c_i\le n,a_i\in\{0,1\}\),保证 \(a_i=1\) 时,\(v_i>0\),保证任何时候骑士战斗力值的绝对值不超过 \(10^{18}\)

分析

从下到上遍历树,一直拿当前节点上的骑士团中最蒻的看看会不会死在这里,再把没死的放到父亲节点上去,并改变他们的战斗力。

骑士的能力是会变的,树上批量修改需要记得标记下传

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
struct node{//骑士战斗值,dist,左右儿子,死亡城池,出生城池,加标记,乘标记
	int val,dist,ch[2],die,c,add,tim;
}t[300005];
struct city{//城池防御值,管辖城池,a,v,深度,当前节点骑士团的根,在这个城池死亡人数
	int val,fa,a,v,dep,rt,ans;
}q[300005];
inline int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
void pushdown(int x){//标记下传,遵循先乘后加原则
	if(t[x].add||t[x].tim!=1){
		if(t[x].ch[0]){
			t[t[x].ch[0]].tim*=t[x].tim;
			(t[t[x].ch[0]].add*=t[x].tim)+=t[x].add;
			(t[t[x].ch[0]].val*=t[x].tim)+=t[x].add;
		}
		if(t[x].ch[1]){
			t[t[x].ch[1]].tim*=t[x].tim;
			(t[t[x].ch[1]].add*=t[x].tim)+=t[x].add;
			(t[t[x].ch[1]].val*=t[x].tim)+=t[x].add;
		}
		t[x].add=0,t[x].tim=1;
	}
}
int merge(int x,int y){//合并前记得先下传标记
	if((!x)||(!y))return x|y;
	pushdown(x),pushdown(y);
	if(t[x].val>t[y].val)swap(x,y);
	t[x].ch[1]=merge(t[x].ch[1],y);
	if(t[t[x].ch[0]].dist<t[t[x].ch[1]].dist)swap(t[x].ch[0],t[x].ch[1]);
	t[x].dist=t[t[x].ch[1]].dist+1;
	return x;
}
signed main(){
	n=read(),m=read();t[0].dist=-1,q[1].dep=1;
	for(int i=1;i<=n;i++){
		q[i].val=read();q[i].rt=-1;
	}	
	for(int i=2;i<=n;i++){
		q[i].fa=read(),q[i].a=read(),q[i].v=read();
		q[i].dep=q[q[i].fa].dep+1;
	}
	for(int i=1;i<=m;i++){
		t[i].val=read(),t[i].c=read();t[i].tim=1;
		if(q[t[i].c].rt!=-1)q[t[i].c].rt=merge(i,q[t[i].c].rt);//合并出生城池相同的士兵
		else q[t[i].c].rt=i;
	}
	for(int i=n;i;i--){//逐个枚举城池
		while(q[i].rt!=-1){
			if(t[q[i].rt].val<q[i].val){//如果堆顶小于防御值
				t[q[i].rt].die=i,pushdown(q[i].rt);//标记死亡后下传
				if(!t[q[i].rt].ch[0])q[i].rt=-1;
				else q[i].rt=merge(t[q[i].rt].ch[0],t[q[i].rt].ch[1]);//合并左右儿子
			}else break;//最小的都没死,则其他的也不会死
		}
		if(i!=1&&q[i].rt!=-1){//更改骑士能力值
			if(q[i].a)t[q[i].rt].tim*=q[i].v,t[q[i].rt].add*=q[i].v,t[q[i].rt].val*=q[i].v;
			else t[q[i].rt].add+=q[i].v,t[q[i].rt].val+=q[i].v;
			pushdown(q[i].rt);
			if(q[q[i].fa].rt!=-1)q[q[i].fa].rt=merge(q[q[i].fa].rt,q[i].rt);
			else q[q[i].fa].rt=q[i].rt;
		}
	}
	for(int i=1;i<=m;i++)q[t[i].die].ans++;//统计答案
	for(int i=1;i<=n;i++)cout<<q[i].ans<<endl;
	for(int i=1;i<=m;i++)cout<<q[t[i].c].dep-q[t[i].die].dep<<endl;
	return 0;
}
posted @ 2023-06-25 13:36  alex_liu09  阅读(16)  评论(0)    收藏  举报