BZOJ4543/3522: [POI2014]Hotel加强版(洛谷P3565)

292 篇文章 1 订阅
281 篇文章 1 订阅

树链剖分 树形DP

%%%neither_nor大佬

BZOJ4543
BZOJ3522(没有权限的同学可以在洛谷上看题目)
洛谷

一道神奇的题目。

BZOJ3522/洛谷P3565

考虑树形DP。定义 f[i][j] 表示 i 子树中深度为j的节点个数, g[i][j] 表示 i 子树中到lca距离相等,且 lca 的深度为 dj 的点对数。(简单点说,就是 (x,y) 到lca的距离相等(假设为d),且在 i 外有一个点到lca的距离也为d。)

转移(应该看看都能懂):

//v是目前边指向的点
g[x][j-1]+=g[v][j];
g[x][j+1]+=f[x][j+1]*f[v][j];
f[x][j+1]+=f[v][j];
ans+=f[x][j-1]*g[v][j];
//实际实现的顺序会不一样

BZOJ4543

n =1e5的条件下,无论是空间还是时间都会炸掉,因此我们考虑进行优化。

接下来就是神奇的时刻。

对于每个点的第一个儿子,f[x][j+1]=f[v][j] g[x][j]=g[v][j+1] ,即把 f 数组往右平移一格,把g数组往左平移一格。我们可以利用指针实现 O(1) 转移。

而对于其它点,转移是 O(dep[x]) 的。所以我们不妨每次把最长的那个儿子进行 O(1) 转移。而这个就用到了长链剖分。

可以证明并不会证明复杂度为 O(n)

代码:

#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
using namespace std;
typedef long long LL;
struct edge{
    int next,to;
}ed[N*2];
int n,k,h[N],fa[N],to[N],sz[N];
LL *f[N],*g[N],myy[N*5],*now=myy+1,ans;
inline char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; return *l++;
}
inline int _read(){
    int x=0; char ch=readc();
    while (!isdigit(ch)) ch=readc();
    while (isdigit(ch)) x=x*10+ch-48,ch=readc();
    return x;
}
inline void addedge(int x,int y){
    ed[++k].next=h[x],ed[k].to=y,h[x]=k;
}
void dfs1(int x){
    for (int i=h[x];i;i=ed[i].next)
        if (ed[i].to!=fa[x]){
            int v=ed[i].to; fa[v]=x,dfs1(v);
            sz[x]=max(sz[v]+1,sz[x]);
            if (sz[to[x]]<sz[v]) to[x]=v;
        }
}
inline void chng(int x){
    f[x]=now,now+=sz[x]+1,g[x]=now+sz[x]+1,now+=sz[x]*2+2;
    //每次给转移数组开(重链深度)的空间
}
void dfs2(int x){
    if (to[x])
        f[to[x]]=f[x]+1,g[to[x]]=g[x]-1,dfs2(to[x]);
    f[x][0]=1,ans+=g[x][0];
    for (int i=h[x];i;i=ed[i].next)
        if (ed[i].to!=fa[x]&&ed[i].to!=to[x]){
            int v=ed[i].to; chng(v),dfs2(v);
            for (int j=sz[v];~j;j--){
                if (j) ans+=f[x][j-1]*g[v][j];
                ans+=g[x][j+1]*f[v][j];
                g[x][j+1]+=f[x][j+1]*f[v][j];
            }
            for (int j=0;j<=sz[v];j++){
                if (j) g[x][j-1]+=g[v][j];
                f[x][j+1]+=f[v][j];
            }
        }
}
int main(){
    n=_read(),sz[0]=-1;
    for (int i=1;i<n;i++){
        int u=_read(),v=_read();
        addedge(u,v),addedge(v,u);
    }
    dfs1(1),chng(1),dfs2(1);
    return printf("%lld\n",ans),0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值