好几天没有做每日一题了,因为周五有场比赛,然后周末出去浪了。
其实是因为这题卡住了。T-T
题意:
给一颗有n个点的树,每个点上有一个权值,记录1为根节点。现在有q次询问,每次询问(u,v,c)表示从u走到v,一路上遇到的点权如果比c大就选择购入珠宝,然后更新这个c,换句话说就是购入的时候的点权只能递增,问一共可以购入多少次。
思路:
参考了题解。
方法是树上倍增。(以前不怎么做这一类题目,因此研究了好久)
首先题目的意思要明确,题目保证从u走到v是往根节点走的,因此我们查找的时候方向是单向的。
那么我们需要知道的就是从u走到v能够购入多少个大小严格递增的珠宝。
我们利用倍增思想,构造一个f[i][j]表示从i出发,购入第2^j个珠宝的点是谁。
那么显然我们有f[i][j]=f[f[i][j-1]][j-1],这是一个递推,如果我们知道f[i][0],就可以推出剩下的了。
接下来考虑计算f[i][0],显然根据定义我们可以知道f[i][0]表示从i出发购入的第一个珠宝的点的位置。
这里我们可以首先判断a[i]与a[fa[i]]的关系,如果有a[fa[i]]>a[i],显然f[i][0]=fa[i]。fa[i]表示i的父亲节点。
否则我们需要往上查找,这里显然可以用倍增去找,由于fa[i]的情况已经算出来了,所以我们可以从fa[i]倍增查找,将k从大到小进行枚举,每次跳2^k判断a[f[fa][k]]与a[i]的大小,如果大于说明跳多了,那么减少k再跳,否则说明跳少了,可以先跳到f[fa][k],然后继续跳。
这样子一遍dfs就可以预处理出f数组来。
最后考虑询问,对于每次询问手上的珠宝c,我们可以在dfs之前先把这个珠宝c作为一个点加入树中。
那么查询的时候,我们倍增地去跳就好了。注意这里要利用深度判断,因为仅通过f数组无法判断终点是否被跳过或者没有。
由于第一次写,所以很多细节其实都是参考别人代码的,下面会做注释。
类型:
树上倍增
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=100050; int f[2*maxn][22],a[2*maxn]; int n,q,head[2*maxn],tot,dep[2*maxn]; struct node{ int u,v,to,next; }p[maxn*4]; struct Node{ int u,v,c; }p1[maxn]; void init(){ memset(head,-1,sizeof(head)); tot=0; } void add(int x,int y){ tot++; p[tot].to=y; p[tot].next=head[x]; head[x]=tot; } void dfs(int x,int fa,int d){ int tmp=fa; for(int k=19;k>=0;k--){ //从大到小枚举跳的长度k if(f[tmp][k]&&a[f[tmp][k]]<=a[x])tmp=f[tmp][k]; //f[tmp][k]=0的话,说明已经跳出树的范围了。如果在范围内,判断跳到的点与x的大小 } if(a[fa]>a[x])f[x][0]=fa; //如果父亲节点比自己大,就设置成父亲节点 else f[x][0]=f[tmp][0]; //如果父亲节点比自己小,就设置成查找出来的那个点 for(int i=1;i<20;i++)f[x][i]=f[f[x][i-1]][i-1]; //递推处理所有长度的情况 dep[x]=d; //记录深度 for(int i=head[x];~i;i=p[i].next){ int y=p[i].to; if(y==fa)continue; dfs(y,x,d+1); } } int main() { cin>>n>>q; for(int i=1;i<=n;i++)cin>>a[i]; init(); for(int i=1;i<=n-1;i++){ int x,y; cin>>x>>y; add(x,y); add(y,x); } for(int i=1;i<=q;i++){ cin>>p1[i].u>>p1[i].v>>p1[i].c; add(n+i,p1[i].u); add(p1[i].u,n+i); a[n+i]=p1[i].c; //对于询问的点加入到树中 } dfs(1,0,1); for(int i=1;i<=q;i++){ int st,ed,ans=0; st=n+i; ed=p1[i].v; for(int k=19;k>=0;k--){ if(dep[f[st][k]]>=dep[ed])st=f[st][k],ans+=(1<<k); //如果当前跳完以后深度大于终点的深度,说明还得跳,并且把本次跳的结果加进来。 } cout<<ans<<endl; } return 0; }