[POI2014][BZOJ3522/4543]Hotel/[JZOJ5357]校门外的树

题目大意

给定一棵 n 个点的树,求树上两两距离相等的点三元组个数。

1n105

题目分析

考虑 dp
fx,i 表示 x 为根的子树内,距离 x i 的点的个数; gx,i 表示以 x 为根的子树中,到 x 距离相等而且到 lca 的距离比 lca x 距离要大 i 的点对个数(说白了就是那些可能的在 x 子树外的第三个点伸出了 x 子树 i 的距离)。
然后在 dp 各个子树之前,我们有 fx,0=1,ans+=gx,0
对于一个子树 y ,我们有

ansgx,i+1gx,i1fx,i+1+=fx,igy,i+1+gx,i+1fy,i+=fx,i+1fy,i+=gy,i+=fy,i

这样我们就可以做到 O(n2) 的时间和空间复杂度。
怎么将时空复杂度优化呢?注意到对于节点 x ,设 y 是我们第一个枚举的子树,那么 gx,i1=gy,i fx,i+1=fy,i ,是数组位移一位的关系。
我们不妨对这棵树长链剖分,然后将重儿子作为第一次枚举的子树,使用类似指针的思路来做到 O(1) 的数组位移,然后其余的子树直接暴力转移。
这样做时间复杂度是 O(n) 的,因为一个点 x 所在的子树被暴力转移当且仅当 x 是一条长链的顶端,而且转移复杂度是 O(depx) 的,也就是和长链长成正比的。因此总的转移复杂度就是所有长链长的和,也就是 O(n) 的。
至于空间复杂度,我们给每条长链顶端分配正比于长链长度的空间就好了,最后也是 O(n) 的。
一些实现细节请读者自行思考。

代码实现

#include 
#include 
#include 

using namespace std;

typedef long long LL;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int N=100005;
const int M=N<<1;
const int E=N<<1;

int last[N],hea[N],depth[N],lng[N],mxl[N],fa[N],fst[N],gst[N];
int tov[E],nxt[E];
LL f[N],g[M];
int n,tot,fcnt,gcnt;
LL ans;

void insert(int x,int y){tov[++tot]=y,nxt[tot]=last[x],last[x]=tot;}

void dfs(int x)
{
    mxl[x]=lng[x]=0;
    for (int i=last[x],y;i;i=nxt[i])
        if ((y=tov[i])!=fa[x])
        {
            depth[y]=depth[fa[y]=x]+1,dfs(y);
            if (mxl[x]1) mxl[x]=mxl[lng[x]=y]+1;
        }
}

void dp(int x,int top)
{
    if (x==top) fst[x]=fcnt,fcnt+=mxl[x]+1,gst[x]=gcnt,gcnt+=mxl[x]<<1|1;
    if (lng[x]) dp(lng[x],top);
    int fptr=fst[top]+depth[x]-depth[top],gptr=gst[top]+mxl[top]-depth[x]+depth[top];
    ++f[fptr],ans+=g[gptr];
    for (int i=last[x],y;i;i=nxt[i])
        if ((y=tov[i])!=fa[x]&&y!=lng[x])
        {
            dp(y,y);
            for (int j=0;j1];
            for (int j=0;j<=mxl[y]&&gptr+j+11|1);++j) ans+=g[gptr+j+1]*f[fst[y]+j];
            for (int j=0;j<=mxl[y]&&gptr+j+11|1)&&fptr+j+11;++j) g[gptr+j+1]+=f[fptr+j+1]*f[fst[y]+j];
            for (int j=0;j1];
            for (int j=0;j<=mxl[y]&&fptr+j+11;++j) f[fptr+j+1]+=f[fst[y]+j];
        }
}

int main()
{
    freopen("tree.in","r",stdin),freopen("tree.out","w",stdout);
    n=read();
    for (int i=1,x,y;i1]=1,dfs(1),fcnt=gcnt=1,dp(1,1),printf("%lld\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}

你可能感兴趣的:(普通动态规划与递推,树链剖分,纪中OJ,BZOJ,计数问题)