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也能做 咱也不敢吭

 

posted @ 2019-10-15 11:31  Tyouchie  阅读(168)  评论(0编辑  收藏  举报