题目大意: 给出一棵树,有若干个骑士,每个骑士会从攻打其所在节点,打完之后打该节点父亲,直到死亡为止,每次攻占一个节点后骑士攻击力会变化,两两骑士之间的攻打相互独立,问每个骑士能打下几个节点以及每个节点处牺牲的骑士数。
题解
单独看每一个城市,假如下面的能上来的骑士都已经上来了,那么要做的就是把不合格的骑士踢掉,然后把剩下的修改一下攻击力然后一把丢上去。
那么踢掉骑士的过程自然就会想到用堆处理了,每次看攻击力最小的是否 < 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]);//士杀
}