CF771C Bear and Tree Jumps

云浅知处

2022-04-25 15:37:59

题解

给定一棵 n 个节点的树与正整数 k,求

\sum_{i=1}^n\sum_{j=1}^n\left\lceil\dfrac{\text{dist}(i,j)}{k}\right\rceil

的值。1\le n\le 2\times 10^5,1\le k\le 5

尽管这题可以有 O(nk^2) 的 dp 做法,但是仅限于边权为 1 的情况。

而点分治可以处理边权任意的情形!考虑当前根为 \text{rt} 的时候怎么算。

对每个 u 处理出来 \text{rt}\to u 路径上的边权和,记作 d_u,那么要求的就是:

\sum_{b_u\neq b_v}\left\lceil\dfrac{d_u+d_v}{k}\right\rceil

其中 b_u 表示 u 是从哪个子树过来的。我们要能快速算出这个东西就行了。

注意到 k 很小,考虑将 d_u 按照 \bmod k 的余数分类。

由于 d_u+d_v=k\lfloor d_u/k\rfloor+(d_u\bmod k)+k\lfloor d_v/k\rfloor +(d_v\bmod k),故

\left\lceil\dfrac{d_u+d_v}{k}\right\rceil=\lfloor d_u/k\rfloor+\lfloor d_v/k\rfloor+\left\lceil\dfrac{(d_u\bmod k) +(d_v\bmod k)}{k}\right\rceil

前一项可以直接算,考虑后一项怎么算。

f_i 为当前子树内满足 d_u\bmod k=iu 的个数,g_i 为前面子树内 \bmod k=i 的个数,那么后一项的和就是

\sum_{i=0}^{k-1}\sum_{j=0}^{k-1}g_if_j\left\lceil\dfrac{i+j}{k}\right\rceil=\sum_{i=0}^{k-1}g_i\left(\sum_{j=1-i}^{k-i}f_j+2\sum_{j=k-i+1}^{k-1}f_j\right)

维护 f,g 以及 f 的前缀和即可。O(nk\log n)

感觉好像可以做到 O((n+k)\log n) 之类的?

#include<bits/stdc++.h>

#define int long long

using namespace std;

inline int read(){
    int x=0,f=1;char c=getchar();
    for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
    for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
    return x*f;
}

const int MN=2e5+5;
const int MK=10;

int f[MK],g[MK],fs[MK],n,k;
vector<int>G[MN];
int sz[MN],rt,all,mx[MN],ans=0;
bool vis[MN];

void calcsiz(int u,int fa){
    mx[u]=0,sz[u]=1;
    for(int v:G[u]){
        if(vis[v]||v==fa)continue;
        calcsiz(v,u),sz[u]+=sz[v],mx[u]=max(mx[u],sz[v]);
    }
    mx[u]=max(mx[u],all-sz[u]);
    if(mx[u]<mx[rt]||rt==0)rt=u;
}

int now=0;

void calcdis(int u,int fa,int de){
    f[de%k]++,now+=(int)(de/k);
    for(int v:G[u]){
        if(v==fa||vis[v])continue;
        calcdis(v,u,de+1);
    }
}

void work(int u){
    memset(g,0,sizeof(g)),memset(f,0,sizeof(f)),memset(fs,0,sizeof(fs));
    vis[u]=1,g[0]=1;int cnt=1,sum=0;
    for(int v:G[u]){
        if(vis[v])continue;
        memset(f,0,sizeof(f)),now=0,calcdis(v,u,1);
        ans+=(now*cnt+sum*sz[v]),sum+=now,cnt+=sz[v];
        fs[0]=f[0];for(int i=1;i<k;i++)fs[i]=fs[i-1]+f[i];
        for(int i=0;i<k;i++){
            if(i==0)ans+=g[i]*(fs[k-1]-fs[0]);
            else ans+=g[i]*(fs[k-i]+2*(fs[k-1]-fs[k-i]));
        }
        for(int i=0;i<k;i++)g[i]+=f[i];
    }
}

void dfz(int u){
    work(u);
    for(int v:G[u]){
        if(vis[v])continue;
        rt=0,all=sz[v],calcsiz(v,u),calcsiz(rt,u),dfz(rt);
    }
}

signed main(void){

#ifndef ONLINE_JUDGE
    freopen("in.in","r",stdin);
#endif

    n=read(),k=read();
    for(int i=1;i<=n-1;i++){
        int u=read(),v=read();
        G[u].push_back(v),G[v].push_back(u);
    }
    all=n,calcsiz(1,0),calcsiz(rt,0),dfz(rt);
    cout<<ans<<endl;

    return 0;
}