bzoj4543

24 篇文章 0 订阅

题意:
给出一棵树,每条边长度为1。问找三个点,使他们两两距离相等有多少方案。
n<=100000(poi原题为5000)

#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iostream>
#define N 110000
#define LL long long
using namespace std;
struct node{int y,nex;}a[2*N];
LL id,*f[N],*g[N],mem[10*N],ans;
int fir[N],len,son[N],dep[N],h[N],mx[N],n,siz[N];
void ins(int x,int y)
{
    a[++len].y=y;a[len].nex=fir[x];fir[x]=len;
}
void dfs(int x,int fa)
{
    dep[x]=dep[fa]+1;mx[x]=x;
    for(int k=fir[x];k;k=a[k].nex)
    {
        int y=a[k].y;
        if(y==fa) continue;
        dfs(y,x);
        if(dep[mx[y]]>dep[mx[son[x]]]) son[x]=y;
        if(dep[mx[y]]>dep[mx[x]]) mx[x]=mx[y];
    }
    siz[mx[x]]=h[x]=dep[mx[x]]-dep[x]+1;
}
void creat_mem()
{
    for(int i=1;i<=n;i++)
    {
        if(mx[i]!=i) continue;
        id+=siz[i]-1;
        f[i]=&mem[id];
        id++;
        g[i]=&mem[id];
        id+=2*siz[i];
        id+=2;
    }
}
void dp(int x,int fa)
{
    if(mx[x]==x) {f[x][0]=1;return;}
    dp(son[x],x);
    f[x]=f[son[x]]-1;
    g[x]=g[son[x]]+1;
    ans+=g[x][0];
    f[x][0]=1;
    for(int k=fir[x];k;k=a[k].nex)
    {
        int y=a[k].y;
        if(y==fa || y==son[x]) continue;
        dp(y,x);

        for(int i=0;i<=h[y];i++) ans+=f[y][i]*g[x][i+1];
        for(int i=1;i<=h[y];i++) ans+=g[y][i]*f[x][i-1];

        for(int i=0;i<=h[y];i++) g[x][i+1]+=f[y][i]*f[x][i+1];
        for(int i=1;i<=h[y];i++) g[x][i-1]+=g[y][i];

        for(int i=0;i<=h[y];i++) f[x][i+1]+=f[y][i];
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        ins(x,y);ins(y,x);
    }
    dfs(1,0);
    creat_mem();
    dp(1,0);
    printf("%lld\n",ans);
    return 0;
}

题解:
膜了题解。。太神了转述一下
显然对于每个方案(x,y,z)有且仅有一个点u使得:
1、x,y,z在u的不同子树中
2、x,y,z到u距离相等
考虑这种dp方法(不然没法做)
f[i][j]表示i子树中与i距离为j的点有多少个
g[i][j]表示i子树已经组合好了多少需要i子树外距离为j的一个点的点对
就是这个意思
这里写图片描述
(x,y)就能对g[i][j]贡献1
n2 转移是容易的
尝试对每个点找一个重孩子,即若x子树中最深叶节点在孩子y子树中,y就是x的重孩子。
定义每个点的高度h[x]为到子树中最深叶节点的距离,将沿重孩子走的链叫重链。
考虑那个 n2 dp的第一次转移
对于x的第一个孩子y,我们是不是令f[x][i]=f[y][i-1],g[x][i]=g[y][i+1]?
发现是平移,利用数组的地址是连续的,将f[x][1]的地址指向f[y][0],将g[x][0]的地址指向g[y][1]就能 O(1) 完成这一部分转移!
除了重孩子外的转移用 O(h[y]) 转移即可
分析时间复杂度。点x转移的复杂度是
O(1+y!=sonh[y])=O(h[y]h[son])=O(h[y]h[x]+1)
将所有的相加,每个x在父亲处为正,在x处为负就全部抵消了!剩下的1作和就是 O(n)
由于同一条重链用的是同一个数组,事先给他分配重链长度的连续地址即可。空间复杂度也是 O(n) 的。用vector实现也是可以的,一条重链用一个vector,对于每个vector记录一下0的位置就好了><
点分治怎么做?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值