题目链接
题意:
给你一棵树和一些人,每个人从某个节点出发走向跟节点,每个节点有一个权值,每个人有一个权值,人在从某个节点向上走时权值会加或乘上一个数,如果人的权值小于节点的,则不能继续向上,统计每个人可以>节点的数量的个数和在某个节点停下的人。
分析:
首先先考虑暴力,直接每个节点的人向上跳,跳的时候计算一下能不能继续向上跳,直接统计就好了。
然后我们会发现,其实向上跳的过程中,好多人是可以合并在一起向上跳的,所以类似归并排序,我们先把人排好序,然后向上跳的时候合并,当然因为如果乘的话保证值为正数,所以加完或乘完之后仍然单调,然后合并两边就好了,这样我们在找最小的时候直接按照顺序跑一遍就完了,但是,这样做还是会被卡掉。
我们又会发现,其实我们并没有必要记录下所有的顺序,即序列可以是无序的,只要能找到最小,能删除最小后仍能找到最小即可,那么我么自然会想到优先队列,但是优先队列并不支持合并,所以我们要换一个可并堆,这里用的左偏树。
然后还有一个问题,因为节点的权值要乘或者加,而对于每个儿子,乘或者加的值还不一样,这是我们可以想到线段树的lazy标记,我们在左偏树上加上lazy标记,合并的时候直接下放标记就好了,首先有了标记之后树型并不改变,所以左偏的性质可以保证,又因为乘的都是正的,所以堆的性质也可以保证,然后直接处理就好了。
代码:
#include <cstdio> #include <algorithm> using namespace std; const int maxn=300000+10; long long fval[maxn];//点的权值 long long gval[maxn];//人的权值 int fa[maxn];//父亲节点 int ss[maxn];//加还是乘 long long ssval[maxn];//加或乘的值 struct E{ int to; int next; }ed[maxn];//边 int head[maxn]; int tot; void J(int a,int b){ tot++; ed[tot].to=b; ed[tot].next=head[a]; head[a]=tot; } E edr[maxn];//在某个点开始的人 int headr[maxn]; int totr; void Jr(int a,int b){ totr++; edr[totr].to=b; edr[totr].next=headr[a]; headr[a]=totr; } int ha;//节点排到的编号 int de[maxn];//深度 struct TR{//树 int ls;//左孩子 int rs;//右孩子 int dis;//距离 int tot; long long lazj;//lazy标记 long long lazx; long long val;//权值 int x; TR(){ dis=-1; val=1e17; } }tr[maxn]; int di[maxn];//在哪个节点停下 int ws[maxn];//这个节点停下来的人 void Dfs(int x,int fa){//处理一下深度 de[x]=de[fa]+1; for(int i=head[x];i;i=ed[i].next) Dfs(ed[i].to,x); } int yz[maxn];//人从哪个节点开始 int ma[maxn];//某个节点的 void push_down(int x){//下放标记 int ls=tr[x].ls; int rs=tr[x].rs; if(ls){//左儿子 tr[ls].val*=tr[x].lazx; tr[ls].val+=tr[x].lazj; tr[ls].lazj*=tr[x].lazx; tr[ls].lazx*=tr[x].lazx; tr[ls].lazj+=tr[x].lazj; } if(rs){右儿子 tr[rs].val*=tr[x].lazx; tr[rs].val+=tr[x].lazj; tr[rs].lazj*=tr[x].lazx; tr[rs].lazx*=tr[x].lazx; tr[rs].lazj+=tr[x].lazj; } tr[x].lazx=1; tr[x].lazj=0; } int Co(int x,int y){ if(!x||!y) return x+y; push_down(x);//下放标记 push_down(y); if(tr[x].val>tr[y].val) swap(x,y); tr[x].rs=Co(tr[x].rs,y); if(tr[tr[x].rs].dis>tr[tr[x].ls].dis) swap(tr[x].ls,tr[x].rs); tr[x].dis=tr[tr[x].rs].dis+1; return x; } int Pop(int x){ push_down(x);//下放标记 return Co(tr[x].ls,tr[x].rs); } long long read(){//读入 long long ans=0; long long f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ ans=(ans<<3)+(ans<<1)+ch-'0'; ch=getchar(); } return f*ans; } int Push(int x,int s,long long val){ ha++;//建立新节点 tr[ha].ls=0; tr[ha].rs=0; tr[ha].lazx=1; tr[ha].lazj=0; tr[ha].val=val; tr[ha].x=s; tr[ha].dis=0; return Co(x,ha); } void Dfs_(int x){ int s=0; for(int i=head[x];i;i=ed[i].next){ Dfs_(ed[i].to); s=1; if(ss[ed[i].to]==0){//将儿子并入并加标记 tr[ma[ed[i].to]].lazj+=ssval[ed[i].to]; tr[ma[ed[i].to]].val+=ssval[ed[i].to]; } else{ tr[ma[ed[i].to]].lazx*=ssval[ed[i].to]; tr[ma[ed[i].to]].lazj*=ssval[ed[i].to]; tr[ma[ed[i].to]].val*=ssval[ed[i].to]; } if(!s&&ma[ed[i].to]){//没有节点之接取下面的编号 ma[x]=ma[ed[i].to]; s=1; } else if(ma[ed[i].to]) ma[x]=Co(ma[x],ma[ed[i].to]); } for(int i=headr[x];i;i=edr[i].next){//加入新的人 if(!s){ ha++; tr[ha].ls=0; tr[ha].rs=0; tr[ha].lazx=1; tr[ha].lazj=0; tr[ha].val=gval[edr[i].to]; tr[ha].x=edr[i].to; ma[x]=ha; tr[ha].dis=0; } else ma[x]=Push(ma[x],edr[i].to,gval[edr[i].to]); s=1; } if(s==0) ma[x]=0; while(ma[x]&&tr[ma[x]].val<fval[x]){//处理信息 ws[x]++; di[tr[ma[x]].x]=x; ma[x]=Pop(ma[x]); } } int main(){ int n,m; scanf("%d%d",&n,&m);//一些读入 for(int i=1;i<=n;i++) scanf("%lld",&fval[i]); for(int i=2;i<=n;i++){ scanf("%d%d%lld",&fa[i],&ss[i],&ssval[i]); J(fa[i],i); } long long js1; int js2; for(int i=1;i<=m;i++){ scanf("%lld%d",&js1,&js2); Jr(js2,i); gval[i]=js1; yz[i]=js2; } Dfs(1,0); Dfs_(1); for(int i=1;i<=n;i++)//输出 printf("%d ",ws[i]); for(int i=1;i<=m;i++) printf("%d ",de[yz[i]]-de[di[i]]);//简单处理一下 return 0; }