BZOJ3222 Hotel&BZOJ4543 Hotel加强版

题目

一棵树,找到三个点他们两两之间距离相同

题解

O ( N 2 ) O(N^2) O(N2)

定义f(u,d)表示以u为根的子树中,与u距离为d的节点数
g(u,d)表示以i为根的子树中,已经找到了两个点,如果在u子树外找到一个距u为d的点就能成为一个合法方案的这两个点的无序点对数
可有转移方程
f [ u ] [ i ] + = f [ v ] [ i − 1 ] f[u][i]+=f[v][i-1] f[u][i]+=f[v][i1]
g [ u ] [ i − 1 ] + = g [ v ] [ i ] g[u][i-1]+=g[v][i] g[u][i1]+=g[v][i]
a n s + = f [ u ] [ i − 1 ] ∗ f [ v ] [ i ] ans+=f[u][i-1]*f[v][i] ans+=f[u][i1]f[v][i]
g [ u ] [ j + 1 ] + = f [ u ] [ j + 1 ] ∗ f [ v ] [ j ] g[u][j+1]+=f[u][j+1]*f[v][j] g[u][j+1]+=f[u][j+1]f[v][j]

O ( N ) O(N) O(N)

假如一个点只有一个儿子,就相当于 f [ u ] [ i ] = f [ v ] [ i − 1 ] , g [ u ] [ i − 1 ] = g [ u ] [ i ] f[u][i]=f[v][i-1],g[u][i-1]=g[u][i] f[u][i]=f[v][i1],g[u][i1]=g[u][i],似乎就只是数组移了个位
那么我们似乎就可以用启发式合并的思想,每次找到最长的一个链通过移位得到,而其他的轻边的贡献就暴力合并。
然而这玩意似乎是 O ( n ) O(n) O(n)的?似乎是轻边长度和就是复杂度?
至于移位,我们可以用链表实现,这样重链的转移就是O(1)的了
这玩意好像叫长链剖分来着?

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=200010;
struct node{
    int u,v,nxt;
}edge[N*2];
int head[N],mcnt;
void add_edge(int u,int v){
    mcnt++;
    edge[mcnt].u=u;
    edge[mcnt].v=v;
    edge[mcnt].nxt=head[u];
    head[u]=mcnt;
}
int mx[N],son[N],fa[N];
void dfs1(int u){
    mx[u]=0;
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].v;
        if(v==fa[u])
            continue ;
        fa[v]=u;
        dfs1(v);
        mx[u]=max(mx[u],mx[v]+1);
        if(mx[v]>mx[son[u]])
            son[u]=v;
    }
}
ll p[N*50];
ll *f[N],*g[N],*cnt=&p[0];
ll ans;
void dfs2(int u){
    if(son[u]){
        f[son[u]]=f[u]+1;
        g[son[u]]=g[u]-1;
        dfs2(son[u]);
    }
    f[u][0]=1;
    ans+=g[u][0];
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].v;
        if(v==fa[u]||v==son[u])
            continue ;
        f[v]=++cnt;
        cnt+=2*mx[v]+1;
        g[v]=++cnt;
        cnt+=2*mx[v]+1;
        dfs2(v);
        for(int j=mx[v];j>=0;j--){
            if(j)
                ans+=f[u][j-1]*g[v][j];
            ans+=g[u][j+1]*f[v][j];
            g[u][j+1]+=f[u][j+1]*f[v][j];
        }
        for(int j=0;j<=mx[v];j++){
            if(j)
                g[u][j-1]+=g[v][j];
            f[u][j+1]+=f[v][j];
        }
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        add_edge(u,v);
        add_edge(v,u);
    }
    dfs1(1);
    f[1]=++cnt;
    cnt+=2*mx[1]+1;
    g[1]=++cnt;
    cnt+=2*mx[1]+1;
    dfs2(1);
    printf("%lld\n",ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值