bzoj4543

来源:互联网 发布:网络实用技术基础答案 编辑:程序博客网 时间:2024/04/27 23:50

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

#include<cstring>#include<cstdlib>#include<cstdio>#include<cmath>#include<iostream>#define N 110000#define LL long longusing 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]为到子树中最深叶节点的距离,将沿重孩子走的链叫重链。
考虑那个n2dp的第一次转移
对于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
原创粉丝点击