POI2014 Hotel加强版

本文详细解析了Hotel加强版问题,数据范围扩大至100000,采用长链剖分优化算法。通过动态规划定义状态dp与g,实现高效求解树上三点等距方案数。文章深入分析了长链剖分的性质及转移过程,附带完整代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Hotel加强版

POI2014

这次数据范围从5000改变到了100000,不能瞎搞了

前置技能:长链剖分

长链剖分有个小性质:所有长链的长度和为O(n)
这个很显然吧?~

题意

同Hotel,于是我复制了一遍

1.一棵n个节点的树

2.选三个点,令这三个点两两之间距离相同

3.问有多少种选点的方案

这次从动态规划角度考虑一下

1.定义
dp[x][j]:x为根,长度为j的链的个数
g[x][j]:x子树中,可以与x子树外,长度为j的链,组合成答案的点对数
特别注意g数组的定义,是x子树中能够与外部长度为j的链组合,并不是内部长度为j

2.状态转移

注意转移顺序(部分不能调换)
ans+=dp[x][j]*g[y][j+1]
↑从x中选一条长为j的链,对于子树y来说就是长为j+1的链了↑

ans+=g[x][j]*dp[y][j-1]
↑x中能与外界长为j的组合,对于子树y来说就是提供j-1的链↑

dp[x][j]+=dp[y][j-1]
↑y中的链到达x时,长度增加了1↑

g[x][j]+=g[y][j+1]
↑x中能与外界长为j的组合,到达儿子y就是长为j+1的链了↑

g[x][j]+=dp[x][j]*dp[y][j-1]
↑y提供长为j-1的链,到达x后,就是长为j的链了↑

3.观察一波
这里状态转移仅与dep有关
由于边的长度为1,链的长度与深度等价

4.长链剖分
定义:son[x]是x下面深度最大的儿子(姑且叫重儿子)
如果我们不把每个点的重儿子转移,即只转移所有的轻儿子,那么转移的复杂度就是O(n)的。
因为每条链只被转移一次,根据题头提到的性质,可以证明转移复杂度为O(n)

那么重儿子的问题怎么办呐?
通过指针转移,公用数组来省去转移的复杂度
先合并重儿子,并且让重儿子需要转移的内存与应该转移到位置的内存相同,这样就可以不用重新赋值了。
相当于计算一次,自动赋值完毕,但是这样重儿子必须优先访问

具体代码

#include<bits/stdc++.h>
using namespace std;
const int M=100005;
int n,dep[M],son[M];
long long space[M*10];
long long *dp[M],*g[M],*tot=space+M,ans;
int head[M],asdf;
struct edge {
    int to,nxt;
} G[M*2];
void add_edge(int a,int b) {
    G[++asdf].to=b;
    G[asdf].nxt=head[a];
    head[a]=asdf;
}
void newnode(int x) {
    dp[x]=tot;
    tot=tot+dep[x]*2+1;
    g[x]=tot;
    tot=tot+dep[x]*2+1;
}
void dfs1(int x,int f) {
    son[x]=0;
    for(int i=head[x]; i; i=G[i].nxt) {
        int y=G[i].to;
        if(y==f)continue;
        dfs1(y,x);
        if(dep[y]>dep[son[x]])son[x]=y;
    }
    dep[x]=dep[son[x]]+1;
}
void dfs2(int x,int f) {
    dp[x][0]=1;
    if(son[x]) {
        dp[son[x]]=dp[x]+1;
        //dp[x][j]+=dp[son[x]][j-1]
        g[son[x]]=g[x]-1;
        //g[x][j]+=g[son[x]][j+1]
        dfs2(son[x],x);
        ans+=g[son[x]][1];
    }
    for(int i=head[x]; i; i=G[i].nxt) {
        int y=G[i].to;
        if(y==f||y==son[x])continue;
        newnode(y);
        dfs2(y,x);
        for(int j=dep[y]; j>=0; j--) {
            ans+=dp[x][j]*g[y][j+1];
            if(j>0) {
                ans+=g[x][j]*dp[y][j-1];
                g[x][j]+=dp[x][j]*dp[y][j-1];
                dp[x][j]+=dp[y][j-1];
            }
            g[x][j]+=g[y][j+1];
        }
    }
}
int main() {
    int a,b;
    scanf("%d",&n);
    for(int i=1; i<n; i++) {
        scanf("%d %d",&a,&b);
        add_edge(a,b);
        add_edge(b,a);
    }
    dfs1(1,0);
    newnode(1);
    dfs2(1,0);
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值