[BZOJ4003]城池攻占:可并堆+lazy tag

首先,这道题有非常简单的暴力写法,只要对每个人暴力一步步往上走即可。如何优化暴力呢?最容易想到的方法是加速转移过程,即倍增,但空间貌似不太够...那么我们从另一个角度考虑:利用重复的信息。同一段路可能被很多人走过,造成了极大的时间浪费,我们可以考虑把要走同一段路的人集中在一起,这样只要走一次,就能同时判断每个人能否走下去了。怎么操作呢?我们以每一个城池为根建立小根堆,堆中的元素是一个个骑士的战斗力,每次判断时,把所有战斗力不足的骑士弹出,剩下的骑士利用可并堆的合并操作合并到下一个城池。那么问题来了,每次攻占城池后,所有存活骑士的战斗力都会发生变化,逐个修改肯定会超时。我们会发现,修改每个骑士战斗力的操作十分类似线段树的区间修改,因此,我们可以利用lazytag优化时间复杂度。完整代码如下。

#include<cstdio>
#define maxn 300010
#define r register
#define cswap(_x,_y) (_x^=_y,_y^=_x,_x^=_y)
#define add(_x,_y) (s[_x]+=_y,tagb[_x]+=_y)//打加法标记
#define mul(_x,_y) (s[_x]*=_y,tagb[_x]*=_y,tagk[_x]*=_y)//打乘法标记
using namespace std;
int n,m,a,fa[maxn],c[maxn],ansn[maxn],ansm[maxn];
int dep[maxn],root[maxn],dis[maxn],ls[maxn],rs[maxn];
long long h[maxn],k[maxn],b[maxn],s[maxn],tagk[maxn],tagb[maxn];
template<class t>void read(r t &x)
{
    r char ch;
    while(ch=getchar(),(ch<'0'||ch>'9')&&ch!='-');
    r bool neg=ch=='-';x=neg?0:ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0';
    if(neg) x=-x;
}
void pushdown(r int x)//切记先乘再加
{
	if(tagk[x]!=1)
	{
		if(ls[x]) mul(ls[x],tagk[x]);
		if(rs[x]) mul(rs[x],tagk[x]);
		tagk[x]=1;
	}
	if(tagb[x])
	{
		if(ls[x]) add(ls[x],tagb[x]);
		if(rs[x]) add(rs[x],tagb[x]);
		tagb[x]=0;
	}
}
int merge(r int x,r int y)
{
	if(!x||!y) return x+y;
	if(s[x]>s[y]) cswap(x,y);
	pushdown(x),rs[x]=merge(rs[x],y);
	if(dis[ls[x]]<dis[rs[x]]) cswap(ls[x],rs[x]);
	dis[x]=dis[rs[x]]+1;
	return x;
}
void dfs(r int x)
{
	ansm[x]=dep[c[x]];
	if(ls[x]) dfs(ls[x]);
	if(rs[x]) dfs(rs[x]);
}
int main()
{
	read(n),read(m);
	for(r int i=1;i<=n;i++) read(h[i]);
	for(r int i=2;i<=n;i++)//把加法和乘法操作转化为kx+b的形式,避免分类讨论
	{
		read(fa[i]),read(a);
		if(a) read(k[i]),b[i]=0;
		else read(b[i]),k[i]=1;
	}
	for(r int i=1;i<=n;i++) dep[i]=dep[fa[i]]+1;
	for(r int i=1;i<=m;i++)
	{
		read(s[i]),read(c[i]),tagk[i]=1;
		root[c[i]]=merge(root[c[i]],i);
	}
	for(r int i=n;i;i--)//自底向上更新答案
	{
		while(root[i]&&s[root[i]]<h[i])//弹出战斗力不足的骑士
		{
			ansn[i]++;
			ansm[root[i]]=dep[c[root[i]]]-dep[i];
			pushdown(root[i]);
			root[i]=merge(ls[root[i]],rs[root[i]]);
		}
		if(root[i])//如果还有存活的骑士,打上标记,合并到下个堆中
		{
			if(b[i]) add(root[i],b[i]);
			if(k[i]!=1) mul(root[i],k[i]);
			root[fa[i]]=merge(root[fa[i]],root[i]);
		}
	}
	dfs(root[0]);//攻下所有城池的骑士保存在以0为根的堆中,会被遗漏,需要特判
	for(r int i=1;i<=n;i++) printf("%d\n",ansn[i]);
	for(r int i=1;i<=m;i++) printf("%d\n",ansm[i]);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值