HHHOJ #1252. 「NOIP 2023 模拟赛 20230716 B」打怪兽 思考--zhengjun

赛时卡了很久,没想出来怎么做。

随便乱搞了个优先级队列,过拍直接走人,然后成为了唯一的 100 分,无语了。

完事后发现这样做确实是对的,而且好写,简单讲一下。

首先需要能够判断打两个怪 \(i,j\) 的优先级:

  • \(a_i\le b_i,a_j\le b_j\),则优先攻击 \(a\) 较小的那个;

  • \(a_i > b_i,a_j > b_j\),则优先攻击 \(b\) 较大的那个;

  • 否则,优先攻击 \(a\le b\) 的那个。

由于有拓扑序的限制,所以考虑从下往上解决问题。

假设已经知道了 \(u\) 的儿子 \(v_1,v_2,\cdots,v_k\) 的攻击序列,且儿子的攻击序列均已有序。

那么显然,\(u\) 只需要把所有的攻击序列归并一下就完事了。

但是,此时需要在序列的最开头加入 \(u\),可能会破坏原来的顺序。

\(u\) 表示的怪为 \(x\),儿子归并后序列的第一项表示的怪为 \(y\)

\(y<x\),说明攻击完 \(x\) 时,可以继续攻击比 \(x\) 还要优的怪。

这里有个隐含条件:攻击 \(x\) 代表着 \(x\) 已经是现有中最优的怪,而 \(y\)\(x\) 优,所以攻击完 \(x\) 后一定会攻击 \(y\)

那么我们不妨直接合并这两个怪,并把 \(y\) 移出原序列。

继续重复上述步骤直到 \(x\) 可以满足序列的单调性为止。

时间复杂度:\(O(n\log n)/O(n\log ^2 n)\),取决于写的是可并堆还是启发式合并。

代码

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e5+10;
int n,m;
struct zj{
	ll a,b;
	zj operator + (const zj &x)const{
		if(x.a>b)return {a+x.a-b,x.b};
		return {a,b+x.b-x.a};
	}
	bool operator < (const zj &x)const{
		int t1=a<=b,t2=x.a<=x.b;
		if(t1^t2)return t1;
		if(t1)return a<x.a;
		return b>x.b;
	}
}a[N];
struct tree{
	zj x;
	int ls,rs,dis;
}t[N];
void merge(int &x,int y){
	if(!x||!y){
		x|=y;return;
	}
	if(t[y].x<t[x].x)swap(x,y);
	merge(t[x].rs,y);
	if(t[t[x].ls].dis<t[t[x].rs].dis)swap(t[x].ls,t[x].rs);
	t[x].dis=t[t[x].rs].dis+1;
}
void pop(int &x){
	int r1=t[x].ls,r2=t[x].rs;
	merge(x=r1,r2);
}
int root[N];
vector<int>to[N];
void dfs(int u,int fa=0){
	for(int v:to[u])if(v^fa){
		dfs(v,u);
		merge(root[u],root[v]);
	}
	for(;root[u]&&t[root[u]].x<a[u];pop(root[u])){
		a[u]=a[u]+t[root[u]].x;
	}
	t[u]={a[u],0,0,1},merge(root[u],u);
}
int main(){
	freopen("monster.in","r",stdin);
//	freopen("monster.out","w",stdout);
	scanf("%d",&n);
	for(int i=2;i<=n;i++)scanf("%lld%lld",&a[i].a,&a[i].b);
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		to[u].push_back(v),to[v].push_back(u);
	}
	dfs(1);
	zj ans={0,0};
	for(;root[1];pop(root[1]))ans=ans+t[root[1]].x;
	cout<<ans.a;
	return 0;
}
posted @ 2023-07-16 19:27  A_zjzj  阅读(21)  评论(0编辑  收藏  举报