CF860E Arkady and a Nobody-men 题解
说实话 这个题目 我觉得 思维难度是有的 这是当时 青岛 杂题选讲的时候 提到的 当时有点迷 回来了 不能咕咕咕了 虽然我已经咕咕咕了很多题目
这个luogu 给出的 题目大意 仍然觉得不够清晰 我这种语文不好的选手理解了好久
大致意思是这样:
给出一个𝑛个节点的有根树,对于每个节点 𝑢,我们定义𝑓(𝑢) = ∑𝑣∈𝑎𝑛𝑐(𝑢) 𝑔(𝑣, 𝑢),其 中,𝑔(𝑎, 𝑏) 表示𝑎的子树当中除𝑏以外深 度不超过𝑏的节点个数。数据范围:n<=5*10^5
现在对于每个𝑖你需要输出𝑓(𝑖)。
考虑怎么写呢 其实和深度有关 我们不免想到BFS序等等 不过问题的瓶颈在于 怎么寻找一个比较好的数据结构维护链的信息 其实有待思考 暂时不是很清楚 所以先弃掉这个思路
那么这个题目 思路还是很多的
做法一:
首先就是树剖的做法 比较鲜明一点 这是一个线段树就可以维护的 直接维护链的信息 区间求和 和修改就行了
考虑 bfs 整棵树,每次插入一层之后回答一层的询问
每个节点都能对它到根的一整条路径上产生贡献,每个节点的答案也是查询它到根的路径上的点权和。考虑树链剖分维护;
时间复杂度𝒪(𝑛log^2𝑛)。
这个适合 码力较好的选手 比如神仙chdy 确实A了这个
考虑做法二
除去自身节点这条限制我们可以暂不考虑,对于 求出的答案最后减掉𝑑𝑒𝑝(𝑖)就可以了。
考虑对于一个节点𝑎,答案等价于∑ 𝑑𝑒𝑝(𝑏)≤𝑑𝑒𝑝(𝑎)𝑑𝑒𝑝(𝑙𝑐𝑎(𝑎, 𝑏))
考虑除𝑎外每个深度不超过𝑎的节点对𝑓(𝑎) 的贡献 显然是从𝑙𝑐𝑎处一直到根节点路径上 的节点作为子树的根时才会被统计到
因此𝑓(𝑎)=𝑓(𝑓𝑎(𝑎))+∑𝑑𝑒𝑝(𝑎)=𝑑𝑒𝑝(𝑏) 𝑑𝑒𝑝(𝑙𝑐𝑎(𝑎,𝑏)) 也就是说 对于每个节点我们现在只需要考虑它同层节点答案的计算
我们将同层的节点拿出(假设其是按照在树中的dfs序排好的 我们可以开一个vector 来解决这个事情 预处理一下
在dfs序有序的情况下 一个节点与它前面 出现的所有节点的 lca 深度是单调递减的。于是这个 问题就可以用单调栈来解决了。这也是单调栈的原因
考虑在一层中从左往右枚举a,同时维护一个 lca 的 单调栈,栈中相邻且相同的元素我们保留一个 并记 录个数。维护一个节点a[i] 和一个 lca 深度b[i] 和一个当前是这一层的哪个节点c[i]
对于新的点a,我们求它与队尾元素的 lca 如果队尾的 lca 深度大于我们求出的深度 就不断地进行合并 也就是sum-=(c[l]-c[l-1])*b[l]
最后再将a自身加入单调栈 同时更新一下 sum+=(c[l]-c[l-1])*b[l] // l 表示的是 队尾元素
总时间复杂度𝒪(𝑛) 这里我们考虑(1)lca 不过 这里我写了倍增 所以复杂度还是nlogn的
为什么做两次呢 左右都需要统计贡献的 所以我们存的时候 按照dfs序 然后我们翻转一下 累计一下右边的贡献即可
考虑还有一种做法 那就是建虚树 口胡一下吧 反正我也不会写这种做法
每次把一层的点拿出来建一棵虚树。也就是优化我们寻找 同层节点 x y 统计答案的过程
此时 我们的关键点 都是叶子 然后考虑 两两之间lca的深度之和可以用树形dp来解决。
令f(i)表示i子树外的所有节点与子树中节点间lca的深度之和。
那么 i 往某个儿子 v 更新时 f(v)=f(i)+dep(i)*(sz(i)-sz(v))
我们的关键点都在叶子。此时的f值就是我们想要统计的答案。
这里放一下 做法二 单调栈的想法;
#include<bits/stdc++.h> using namespace std; template<typename T>inline void read(T &x) { x=0;T f=1,ch=getchar(); while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x*=f; } typedef long long ll; const int N=500010; int n,tot,x,l,root,lin[N],s[N],d[N],b[N],c[N],f[N][21]; ll ans[N]; struct gg { int y,next; }a[N<<1]; vector<int>g[N]; inline void add(int x,int y) { a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot; } inline void dfs(int x) { g[d[x]].push_back(x);//同层节点 丢进去; for(int i=lin[x];i;i=a[i].next) { int y=a[i].y; d[y]=d[x]+1; dfs(y); } } inline int lca(int x,int y) { if(d[x]>d[y]) swap(x,y); for(int i=20;i>=0;i--) { if(d[f[y][i]]>=d[x]) { y=f[y][i]; } } if(x==y) return x; for(int i=20;i>=0;i--) { if(f[y][i]!=f[x][i]) { y=f[y][i],x=f[x][i]; } } return f[x][0]; } inline void slove(int i) { ll sum=0; for(int j=0;j<g[i].size();j++) { int x=g[i][j]; if(!j) s[l=1]=x,b[l]=c[l]=0; else { while(1) { int Lca=lca(x,s[l]); if(b[l]<=d[Lca]) { s[++l]=x,b[l]=d[Lca]+1,c[l]=j; break; } sum-=1ll*(c[l]-c[l-1])*b[l]; l--; } sum+=1ll*(c[l]-c[l-1])*b[l]; } ans[x]+=sum; } } int main() { // freopen("1.in","r",stdin); read(n); for(int i=1;i<=n;i++) { read(x); if(!x) root=i,f[i][0]=i; else add(x,i),f[i][0]=x; } dfs(root); for(int i=1;i<=20;i++) { for(int j=1;j<=n;j++) { f[j][i]=f[f[j][i-1]][i-1]; } } for(int i=1;i<n;i++) { if(!g[i].size()) break; for(int j=0;j<g[i].size();j++) ans[g[i][j]]+=ans[f[g[i][j]][0]]+i; slove(i); reverse(g[i].begin(),g[i].end()); slove(i); } for(int i=1;i<=n;i++) cout<<ans[i]<<' '; puts(""); return 0; }
有神仙说lct也能做 咱也不敢吭