[洛谷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,这是他的城池攻占:666

原文地址:https://www.cnblogs.com/cjoierljl/p/8641030.html

时间: 2024-10-10 15:48:14

[洛谷P3261] [JLOI2015]城池攻占(左偏树)的相关文章

Luogu P3261 [JLOI2015]城池攻占

dfs时,用可并的小根堆去维护所有活下来的骑士:更新时就直接往外弹即可:然后堆上要维护乘法和加法标记. #include<iostream> #include<cstdio> #define ll long long #define R register int using namespace std; namespace Luitaryi { inline ll g() { register ll x=0,f=1; register char s; while(!isdigit(

关于左偏树的一些东东

大概所有的预备知识这里都有https://baike.baidu.com/item/%E5%B7%A6%E5%81%8F%E6%A0%91/2181887?fr=aladdin 例题1:洛谷 P3377 [模板]左偏树(可并堆) 383通过 1.2K提交 题目提供者HansBug 站长团 标签 难度提高+/省选- 时空限制1s / 128MB 提交 讨论 题解 最新讨论更多讨论 加了路径压缩就WA,路过dal… 左偏树用指针写会MLE吗..… m,n写反了也可以过,数据有… 哪位大神有pbds库

左偏树(可并堆)

"左偏"树? 左偏树其实是一种可并堆,它可以 \(O(log_2 n)\) 合并两个堆. 那左偏?也就是说他左边肯定有什么东西比右边大-- 别着急,在左偏树上有一个叫距离的东西: 个点的距离,被定义为它子树中离他最近的外节点到这个节点的距离(这与树的深度不同) 其中我们定义一个节点为外节点,当且仅当这个节点的左子树和右子树中的一个是空节点.(注意外节点不是叶子节点) 这幅图中的这三个节点都是外节点. 而左偏树指的就是就是一个节点的左儿子的距离一定大于等于右儿子的距离. 例如下面就是一棵

bzoj4003[JLOI2015]城池攻占

bzoj4003[JLOI2015]城池攻占 题意: 有n个城池组成根节点为1的树,m个人,当一个人的战斗力大于等于攻打城市的防御力,就能攻占这个城市,来到这个城市的父节点,否则该人会牺牲在这个城市.当一个城市被攻占时,会使攻占的人的战斗力加或乘上某个数.现在给出m个人的最开始攻打的城市和初始战斗力,求在每个城市的牺牲人数和每个人一共攻打几个城市.注意这m个人处在不同的时空,即攻击互不影响,且每个人会一直往上攻打除非牺牲或到达根节点. 题解: 由于对一些数乘一个正数或加一个数这些数的相对大小不变

【BZOJ 4003】 [JLOI2015]城池攻占

4003: [JLOI2015]城池攻占 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 206 Solved: 89 [Submit][Status][Discuss] Description 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池. 这 n 个城池用 1 到 n 的整数表示.除 1 号城池外,城池 i 会受到另一座城池 fi 的管辖, 其中 fi 小于i.也就是说,所有城池构成了一棵有根树.这 m 个骑士用 1

【左偏树+延迟标记+拓扑排序】BZOJ4003-城池攻占

[题目大意] 有n个城市构成一棵树,除1号城市外每个城市均有防御值h和战斗变化参量a和v. 现在有m个骑士各自来刷副本,每个其实有一个战斗力s和起始位置c.如果一个骑士的战斗力s大于当前城市的防御值h,则可攻破这个城市,并前往它的管辖地(即树上的父亲),同时,战斗力s发生如下变化: ①如被攻占城市a=0,则s+=v: ②如果a=0,s*=v. 输出:每个骑士能够攻占的城市数量,以及每个城市有多少个骑士牺牲了. [思路] 昨天写了一遍这道题,当时用的是DFS,今天用拓扑重写一遍.思路如下: 从下往

BZOJ 4003([JLOI2015]城池攻占-带标记可合并堆)[Template:带标记可合并堆]

4003: [JLOI2015]城池攻占 Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 490  Solved: 181 [Submit][Status][Discuss] Description 小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池. 这 n 个城池用 1 到 n 的整数表示.除 1 号城池外,城池 i 会受到另一座城池 fi 的管辖, 其中 fi <i.也就是说,所有城池构成了一棵有根树.这 m 个骑士用

BZOJ4003 JLOI2015城池攻占

用左偏树模拟攻占的过程,维护最小值,最多入和出m次,每次log复杂度. 1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=3e5+10; 4 typedef long long ll; 5 ll w[N],v[N],mul[N],add[N],h[N]; 6 int l[N],r[N],dis[N],flag[N],c[N],rt[N],head[N],f[N],d[N],die[N],a[N],n,m,cnt; 7

左偏树初步 bzoj2809 &amp; bzoj4003

看着百度文库学习了一个. 总的来说,左偏树这个可并堆满足 堆的性质 和 左偏 性质. bzoj2809: [Apio2012]dispatching 把每个忍者先放到节点上,然后从下往上合并,假设到了这个点 总值 大于 预算,那么我们把这个 大根堆 的堆顶弹掉就好了,剩下的就是可合并堆. 感谢prey :) 1 #include <bits/stdc++.h> 2 #define rep(i, a, b) for (int i = a; i <= b; i++) 3 #define dr