【HDU6326】Monster Hunter(树上一类全序问题)

本文探讨了一种全序问题,即如何在有限血量下,按照特定顺序打怪兽以使得血量损失最小。通过贪心策略将怪兽分为两类,并分析了在没有树限制和存在树限制时的最优顺序。在树结构限制下,利用并查集和可删堆进行优化。最终给出了解决此类问题的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先考虑没有树的限制,即我们可以任意安排顺序打怪兽,那么这就是一个全序问题。

考虑在某种顺序下,假设初始血量为 s t st st,那么打到第 i i i 个怪物时剩余的血量就是 s t + ∑ j = 1 i − 1 ( b j − a j ) st+\sum\limits_{j=1}^{i-1}(b_j-a_j) st+j=1i1(bjaj),如果设 s u m i = ∑ j = 1 i − 1 ( b j − a j ) sum_i=\sum\limits_{j=1}^{i-1}(b_j-a_j) sumi=j=1i1(bjaj),那么我们就需要保证 ∀ i , s t + s u m i ≥ a i \forall i,st+sum_i\geq a_i i,st+sumiai,于是在这种顺序下 s t st st 的最小值为 max ⁡ i = 1 n ( a i − s u m i ) \max\limits_{i=1}^n(a_i-sum_i) i=1maxn(aisumi)。我们要使 s t st st 最小。

考虑在当前的某种顺序下,交换 i i i i + 1 i+1 i+1 什么时候不优。由于交换 i i i i + 1 i+1 i+1 不会对 i + 1 i+1 i+1 后面造成影响,所以我们只需要考虑让 max ⁡ j = 1 i + 1 ( a j − s u m j ) \max\limits_{j=1}^{i+1}(a_j-sum_j) j=1maxi+1(ajsumj) 最小即可。设 p r e = max ⁡ j = 1 i − 1 ( a j − s u m j ) pre=\max\limits_{j=1}^{i-1}(a_j-sum_j) pre=j=1maxi1(ajsumj) s = s u m i s=sum_i s=sumi

交换前: a n s 1 = max ⁡ ( p r e , a i − s , a i + 1 − ( s + ( b i − a i ) ) ) = max ⁡ ( p r e , a i − s , a i + 1 − s + a i − b i ) ans_1=\max(pre,a_i-s,a_{i+1}-(s+(b_i-a_i)))=\max(pre,a_i-s,a_{i+1}-s+a_i-b_i) ans1=max(pre,ais,ai+1(s+(biai)))=max(pre,ais,ai+1s+aibi)

交换后: a n s 2 = max ⁡ ( p r e , a i + 1 − s , a i − ( s + ( b i + 1 − a i + 1 ) ) ) = max ⁡ ( p r e , a i + 1 − s , a i − s + a i + 1 − b i + 1 ) ans_2=\max(pre,a_{i+1}-s,a_i-(s+(b_{i+1}-a_{i+1})))=\max(pre,a_{i+1}-s,a_{i}-s+a_{i+1}-b_{i+1}) ans2=max(pre,ai+1s,ai(s+(bi+1ai+1)))=max(pre,ai+1s,ais+ai+1bi+1)

我们要求的是什么时候 a n s 1 ≤ a n s 2 ans_1\leq ans_2 ans1ans2

直接讨论好像有点难处理,如果我们知道 a i − b i a_i-b_i aibi a i + 1 − b i + 1 a_{i+1}-b_{i+1} ai+1bi+1 的正负性就好了。

这里有一个贪心。我们将怪兽分成两类: b i > a i b_i>a_i bi>ai 的和 b i ≤ a i b_i\leq a_i biai 的,前一类打完会加血,后一类打完会扣血。

我们肯定先打加血再打扣血的,因为先打扣血的没有任何好处。所以 b i > a i b_i>a_i bi>ai 一定放在前面, b i ≤ a i b_i\leq a_i biai 的一定放在后面。

所以我们可以对这两类分类讨论:

  • 第一类:若 b i > a i b_i>a_i bi>ai b i + 1 > a i + 1 b_{i+1}>a_{i+1} bi+1>ai+1。那么由上面的式子可知 a n s 1 ≤ a n s 2 ans_1\leq ans_2 ans1ans2 的一个充分条件为 a i ≤ a i + 1 a_i\leq a_{i+1} aiai+1。于是得到结论这一类中先打 a i a_i ai 小的一定更优,因为如果你先打了某个 a i a_i ai 大的再打某个 a i a_i ai 小的,那么交换这两个的打的顺序一定会更优。
  • 第二类:若 b i ≤ a i b_i\leq a_i biai b i + 1 ≤ a i + 1 b_{i+1}\leq a_{i+1} bi+1ai+1。那么由上面的式子可知 a n s 1 ≤ a n s 2 ans_1\leq ans_2 ans1ans2 的一个充分条件为 b i ≥ b i + 1 b_i\geq b_{i+1} bibi+1。于是得到结论这一类中先打 b i b_i bi 大的一定更优,因为如果你先打了某个 b i b_i bi 小的再打某个 b i b_i bi 大的,那么交换这两个的打的顺序一定会更优。

所以我们得到结论:先打 b i > a i b_i>a_i bi>ai 的一定比先打 b i ≤ a i b_i\leq a_i biai 的更优, b i > a i b_i>a_i bi>ai 的中先打 a i a_i ai 小的一定更优, b i ≤ a i b_i\leq a_i biai 中先打 b i b_i bi 大的一定更优。这样每一个怪兽都有了一个优先级。

那么如果加入了树的限制会怎么样呢?和 AGC023F 一样,直接用并查集+可删堆维护即可。

代码如下:(常数有点大,2995ms卡过去的

#include<bits/stdc++.h>

#define N 100010
#define ll long long

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int rt[N];
int t,n,fa[N];
int cnt,head[N],nxt[N<<1],to[N<<1];
bool del[N];
ll a[N],b[N];

inline void adde(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

void dfs(int u)
{
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa[u]) continue;
		fa[v]=u;
		dfs(v);
	}
}

struct cmp
{
	inline bool operator()(const int &x,const int &y) const
	{
		if((b[x]>a[x])^(b[y]>a[y])) return b[x]>a[x];
		if(b[x]>a[x])
		{
			if(a[x]!=a[y]) return a[x]<a[y];
			return x<y;
		}
		else
		{
			if(b[x]!=b[y]) return b[x]>b[y];
			return x<y;
		}
	}
};

set<int,cmp>s;

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

int main()
{
	t=read();
	while(t--)
	{
		n=read();
		cnt=0;
		for(int i=1;i<=n;i++)
			head[i]=0,del[i]=0,rt[i]=i;
		for(int i=2;i<=n;i++)
			a[i]=read(),b[i]=read();
		for(int i=1;i<n;i++)
		{
			int u=read(),v=read();
			adde(u,v),adde(v,u);
		}
		dfs(1);
		del[1]=1;
		for(int i=2;i<=n;i++) s.insert(i);
		ll sum=0,ans=0;
		while(!s.empty())
		{
			const int u=(*s.begin());
			s.erase(s.begin());
			const int f=find(fa[u]);
			if(del[f])
			{
				del[u]=1;
				ans=max(ans,a[u]-sum);
				sum+=b[u]-a[u];
				continue;
			}
			s.erase(f);
			rt[u]=f;
			ll na,nb;
			if(a[f]>a[f]-b[f]+a[u]) na=a[f],nb=b[f]+b[u]-a[u];
			else na=a[f]-b[f]+a[u],nb=b[u];
			a[f]=na,b[f]=nb;
			s.insert(f);
		}
		printf("%lld\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值