JLOI 2015 城池攻占 题解

题目传送门

题目大意: 给出一棵树,有若干个骑士,每个骑士会从攻打其所在节点,打完之后打该节点父亲,直到死亡为止,每次攻占一个节点后骑士攻击力会变化,两两骑士之间的攻打相互独立,问每个骑士能打下几个节点以及每个节点处牺牲的骑士数。

题解

单独看每一个城市,假如下面的能上来的骑士都已经上来了,那么要做的就是把不合格的骑士踢掉,然后把剩下的修改一下攻击力然后一把丢上去。

那么踢掉骑士的过程自然就会想到用堆处理了,每次看攻击力最小的是否 < h [ x ] < h[x] <h[x],如果是就踢掉,否则停止。

那么怎么将剩下的骑士丢上去呢,其实这个过程就是合并堆啦,那么就用可并堆做即可,我选择的实现方式是左偏树

那么修改骑士的攻击力这一操作,给整个堆打上标记就好了,具体实现就看代码吧,有详细注释:

#include <cstdio>
#include <cstring>
#define maxn 300010
#define ll long long

ll h[maxn],v[maxn];
int n,m,a[maxn];
int ans1[maxn],ans2[maxn];//ans1是城市杀得骑士数,ans2是骑士打败的城市数
struct node{
    node *zuo,*you,*fa;
    int dist,pos,st;ll val,latec,latej;
    //val:值   latec:乘法标记     latej:加法标记
    node(ll x,int y,int z):zuo(NULL),you(NULL),fa(NULL),val(x),pos(y),st(z),dist(0),latej(0ll),latec(1ll){};
    void check()//将latec和latej丢下去
    {
        val*=latec;val+=latej;
        if(zuo!=NULL)zuo->latec*=latec,zuo->latej=zuo->latej*latec+latej;
        if(you!=NULL)you->latec*=latec,you->latej=you->latej*latec+latej;
        //看完下面代码之后就可以发现父亲那里来的late一定是比自己的late要晚
        //的,所以可以采用这种合并late标记的方式
        latec=(latej=0)+1;
    }
};
node *ch[maxn];
node *findroot(node *x){return x->fa==NULL?x:findroot(x->fa);}
void swap(node *&x,node *&y){node *t=x;x=y;y=t;}
void swap(ll &x,ll &y){ll t=x;x=y;y=t;}
void up_dist(node *x)
{
    if(x->you!=NULL)x->dist=x->you->dist+1;
    if(x->fa!=NULL)up_dist(x->fa);
}
node *merge(node *x,node *y)
{
    if(x==NULL)return y;
    if(y==NULL)return x;
    x->check();
    //记得x要check一下↓,y保证是已经check过的,结合代码就可以理解了
    if(x->val>y->val)swap(x,y);
    x->you=merge(x->you,y);
    x->you->fa=x;
    if(x->zuo!=NULL&&x->you!=NULL&&x->zuo->dist<x->you->dist)swap(x->zuo,x->you);
    if(x->zuo==NULL&&x->you!=NULL)swap(x->zuo,x->you);
    up_dist(x);
    return x;
}
struct nod{int x,y,next;};
nod e[maxn];
int first[maxn];
void buildroad(int x,int y)//邻接表建边
{
    static int len=0;
    e[++len]=(nod){x,y,first[x]};
    first[x]=len;
}
int deep[maxn];
node *pop(node *&x)
{
    if(x==NULL)return NULL;
    if(x->zuo!=NULL)x->zuo->check();
    if(x->you!=NULL)x->you->check();
    node *p=merge(x->zuo,x->you);
    if(p!=NULL)p->fa=NULL;
    node *xx=x;x=p;
    xx->zuo=xx->you=NULL;xx->dist=0;//就是因为漏了这一行,WA的莫名其妙
    return xx;
}
void dfs(int x,int fa)
{
    for(int i=first[x];i;i=e[i].next)
    deep[e[i].y]=deep[x]+1,dfs(e[i].y,x);
    if(ch[x]!=NULL)
    {
        node *p=pop(ch[x]);
        while(p!=NULL&&p->val<h[x])//假如这个骑士要踢掉
        {
            ans2[p->pos]=deep[p->st]-deep[x];//记录下该骑士的答案
            ans1[x]++;//该城市击杀的骑士数+1
            p=pop(ch[x]);
        }
        ch[x]=merge(ch[x],p);
    }
    if(fa!=0&&ch[x]!=NULL)
    {
        if(a[x]==0)ch[x]->latej+=v[x];
        else ch[x]->latej*=v[x],ch[x]->latec*=v[x];
        ch[x]->check();//进去的时候已经check过了,所以merge里面就不用check了
        ch[fa]=merge(ch[fa],ch[x]);
    }
}

int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%lld",&h[i]);
    for(int i=2;i<=n;i++)
    {
        int x;
        scanf("%d %d %lld",&x,&a[i],&v[i]);
        buildroad(x,i);//父亲向儿子连边
    }
    for(int i=1;i<=m;i++)
    {
        ll x;int y;
        scanf("%lld %d",&x,&y);
        node *p=new node(x,i,y);
        ch[y]=merge(ch[y],p);//ch[y]表示y这座城的骑士组成的堆的根
    }
    deep[1]=1;
    dfs(1,0);
    node *pp;//剩下这步是统计一路杀到底生生不息还没死的骑士的答案
    while(pp=pop(ch[1]))ans2[pp->pos]=deep[pp->st];
    for(int i=1;i<=n;i++)
    printf("%d\n",ans1[i]);//城杀 
    for(int i=1;i<=m;i++)
    printf("%d\n",ans2[i]);//士杀 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值