[洛谷P3261] [JLOI2015]城池攻占

不得不说,这道题目是真的难,真不愧它的“省选/NOI-”的紫色大火题!!!

花了我晚自习前半节课看题解,写代码,又花了我半节晚自习调代码,真的心态爆炸。基本上改得和题解完全一样了我才过了这道题!真的烦。没事,那接下来我来完全把这道题搞透。

Part 1 理解题目

至少我一开始不知道为什么要用左偏树,甚至我看题解一开始也都没弄懂,所以先把题目弄清楚。
首先我们由题可以知道,这要求我们从建好的树的叶子节点开始往上推,有些骑士到特定的点才会出现,check一下骑士能否攻占城池,再记录进答案,更新战斗力,这就很容易想到左偏树可并堆了。

Part 2 解题思想

既然每到一个点会出现一堆的新骑士,所以我们可以在那些点连一些“隐藏边”,到这个点时用链式前向星扫一遍加到一个小根堆中,然后把这个点以下的所有剩下的骑士合并到这个堆中(板子),然后在check时挨个弹出堆顶,如果不能占领就记入答案,能占领我们就要考虑更新骑士,我们不可能直接更新整个堆中的骑士,这样会被硬生生卡成O(n)的修改,所以我们考虑放一个lazy标记在堆顶,每一次合并和删除的时候再下放就可以了。

part 3 code

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<ctime>
#include<queue>
#include<stack>
#define lst long long
#define rg register
#define N 300050
using namespace std;

int n,m,cnt;
bool type[N];
int fir[N],deep[N],up[N],dead[N];
lst key[N],def[N],v[N],mul[N],plu[N];
struct edge{
    int to,nxt;
}a[N],b[N];
int head[N],ft[N],ls[N],rs[N],dis[N];

inline lst read()
{
    rg lst s=0,m=1;rg char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')m=-1,ch=getchar();
    while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    return m*s;
}

void cover(rg int A,rg lst c,rg lst j)
{
    if(!A)return;
    key[A]*=c,key[A]+=j;
    mul[A]*=c,plu[A]*=c,plu[A]+=j;
}

void pushdown(rg int A)
{
    cover(ls[A],mul[A],plu[A]);
    cover(rs[A],mul[A],plu[A]);
    mul[A]=1,plu[A]=0;
}

int Merge(rg int A,rg int B)
{
    if(!A||!B)return A+B;
    if(key[A]>key[B])swap(A,B);
    pushdown(A),pushdown(B);
    rs[A]=Merge(rs[A],B);
    if(dis[ls[A]]<dis[rs[A]])swap(ls[A],rs[A]);
    dis[A]=dis[rs[A]]+1;
    return A;
}

int Delete(rg int A)
{
    pushdown(A);
    return Merge(ls[A],rs[A]);
}

int dfs(rg int now,rg int fm)
{
    rg int A=0,B;
    deep[now]=deep[fm]+1;
    for(rg int i=ft[now];i;i=b[i].nxt)A=Merge(A,b[i].to);
    for(rg int i=head[now];i;i=a[i].nxt)
    {
        B=dfs(a[i].to,now);
        A=Merge(A,B);
    }
    while(key[A]<def[now]&&A)
    {
        dead[now]++;up[A]=deep[now];
        A=Delete(A);
    }
    if(type[now])cover(A,v[now],0);
    else cover(A,1,v[now]);
    return A;
}

int main()
{
    n=read(),m=read();
    for(rg int i=1;i<=n;++i)def[i]=read();
    for(rg int i=2;i<=n;++i)
    {
        rg int go=read();
        a[++cnt]=(edge){i,head[go]};head[go]=cnt;
        type[i]=read(),v[i]=read();
    }cnt=0;
    for(rg int i=1;i<=m;++i)
    {
        key[i]=read(),fir[i]=read();
        b[++cnt]=(edge){i,ft[fir[i]]};ft[fir[i]]=cnt;
    }
    dfs(1,0);
    for(rg int i=1;i<=n;++i)printf("%d\n",dead[i]);
    for(rg int i=1;i<=m;++i)printf("%d\n",deep[fir[i]]-up[i]);
    return 0;
}

到此为止,顺便膜拜一下大佬zsy,这是他的城池攻占:租酥雨的左偏树城池攻占

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值