题意:给出一颗树,有两种操作:
1. mark u 标记结点u
2.query u 询问离u最近的且被标记的祖先结点是哪个
让你输出所有询问的和。
显然一个暴力的做法是每次都直接修改,然后查询的话就一个一个地向祖先查询,直到一个被标记过的点.
让我们来优化一下这个暴力.
类似于一个题https://www.luogu.com.cn/problem/P1653
考虑反着搞,如果一个点的最早被标记时间比当前时间大或者根本没有被标记,那么这个点以后都不会用到了,可以被路径压缩掉.
#include<cmath> #include<cstdio> #include<iostream> #include<cstdlib> #include<algorithm> #include<cstring> #include<map> #include<queue> #include<set> #include<vector> #include<bitset> using namespace std; const int maxn=100005; int vis[maxn],fa[maxn],n,Q,cnt; long long ans; struct query { int t,id; }q[maxn]; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } inline void write(long long a) { if(a<0) { char a='-',b='1'; putchar(a); putchar(b); } else { if(a>=10) write(a/10); putchar(a%10+'0'); } } int find(int id,int t) { return vis[id]<t?id:fa[id]=find(fa[id],t); } int main() { while(scanf("%d%d",&n,&Q)==2&&n&&Q) { for(int i=2;i<=n;++i) fa[i]=read(),vis[i]=1e9; ans=cnt=0; for(int i=1;i<=Q;++i) { char ch; cin>>ch; int x=read(); if(ch=='Q') q[++cnt].t=i,q[cnt].id=x; else vis[x]=min(vis[x],i); } for(int i=cnt;i>=1;--i) ans+=find(q[i].id,q[i].t); write(ans); putchar('\n'); } return 0; }
当然,这仍然是个暴力(虽然在Aizu上面过了),比如树退化成一条链且每一次都查询叶子节点而不修改,就可以被卡到n方.
这里有一篇线段树维护的,大家可以看一下.
https://www.cnblogs.com/zeroxf/p/6749091.html