牛客CSP-S提高组赛前集训营4 B 路径计数机

传送门

这道题其实不用换根(我也不会打换根)

对于(a,b)(c,d)这两条路径,我们考虑有哪些情况:

1. LCA(c,d)在LCA(a,b)的子树中。

2. LCA(c,d)不在LCA(a,b)的子树中。

对于第一种情况,我们要保证LCA(c,d)不在a到b的路径上。

对于第二种情况,c到d的路径不经过连接LCA(a,b)和其父亲的边。

然后就先两次dfs神奇处理出我们需要的东西,代码很好理解。(我也只能理解了)

主要是思维很神奇。

#include<bits/stdc++.h>
#define LL long long
#define N 3003
#define re register
using namespace std;
int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
} 
struct EDGE{
    int nextt,to;
}w[N*2];
int tot=0;
int n,p,q;
int head[N];
LL fq[N],fp[N];//以i为lca,长度为q/p的路径数 
LL gq[N],gp[N];//经过了i与其父亲的连边,长度为q/p的路径数 
LL f[N][N],g[N][N];//i的子树中距离根为j的个数,不在i的子树中距离根为j的个数 
void add(int a,int b)
{
    tot++;
    w[tot].nextt=head[a];
    w[tot].to=b;
    head[a]=tot;
}
//fp,fq,gp,gq更新时均是用了乘法原理
//要有把子树紫薯一个个丢入的前缀思想 
void dfs1(int x,int fa)
{
    f[x][0]=1;
    for(int i=head[x];i;i=w[i].nextt)
    {
        int v=w[i].to;
        if(v==fa)continue;
        dfs1(v,x);
        for(int j=1;j<=p;++j)fp[x]+=f[v][p-j]*f[x][j-1];
        for(int j=1;j<=q;++j)fq[x]+=f[v][q-j]*f[x][j-1];
        for(int j=1;j<=max(q,p);++j)f[x][j]+=f[v][j-1];
    }
}
void dfs2(int x,int fa)
{
    for(int i=1;i<=p;++i)gp[x]+=f[x][p-i]*g[x][i];
    for(int i=1;i<=q;++i)gq[x]+=f[x][q-i]*g[x][i];
    for(int i=head[x];i;i=w[i].nextt)
    {
        int v=w[i].to;
        if(v==fa)continue;
        g[v][1]=1;
        for(int j=2;j<=max(p,q);++j)g[v][j]+=g[x][j-1]+f[x][j-1]-f[v][j-2];
        dfs2(v,x);
    }
}
int main()
{
    n=read();p=read();q=read();
    for(int i=1;i<n;++i)
    {
        int a=read(),b=read();
        add(a,b);add(b,a);
    }
    dfs1(1,1);dfs2(1,1);
    LL sum1=0,sum2=0;
    for(int i=1;i<=n;++i)sum1+=fq[i],sum2+=fp[i];
    LL ans=sum1*sum2;
    for(int i=1;i<=n;++i)ans-=(fq[i]*fp[i]+fq[i]*gp[i]+fp[i]*gq[i]);
    printf("%lld\n",ans*4);
} 
/*
*/
View Code
posted @ 2019-11-06 20:56  yyys  阅读(211)  评论(0编辑  收藏  举报