好几天没有做每日一题了,因为周五有场比赛,然后周末出去浪了。
其实是因为这题卡住了。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;
}
京公网安备 11010502036488号