P4582 [FJOI2014] 树的重心

题意

给一颗 \(n\) 个点树,问祂有多少个生成子图,重心和原树完全一致。
\(n\le 200\)

思路

先把原树的重心求出来,然后分类讨论。

\(Part1\):一个重心

把重心当作根,跑树形 \(DP\)。设状态 \(f_{i,j}\) 表示以 \(i\) 为根的子树种选了 \(j\) 个点的方案数(\(i\) 点也选了),转移直接跑树形背包。
然后枚举 \(x\) 表示删掉 \(x\) 个点,那么每个子树的大小就不能超过 \(\lfloor\frac{n-x-1}2\rfloor\),在跑一边背包即可。

\(Part2\):两个重心

这两个重心是连着的,考虑将祂们之间的边切断,然后分别作为根,跑树形 \(DP\)。设状态 \(f_{i,j}\) 表示以 \(i\) 为根的子树种选了 \(j\) 个点的方案数(\(i\) 点也选了),转移直接跑树形背包。为了使重心依旧是那两个点,砍完边后的两棵树上选的点数应是一样的,这样统计答案就很简单了。

代码

/*
Luogu P4582 [FJOI2014] 树的重心
2026-04-13
*/
#include<bits/stdc++.h>
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T&x){
        x=0;char c=getchar();bool f=0;
        while(!isdigit(c)) c=='-'?f=1:0,c=getchar();
        while(isdigit(c)) x=x*10+c-'0',c=getchar();
        f?x=-x:0;
    }
    template<typename T>
    inline void write(T x){
        if(x==0){putchar('0');return ;}
        x<0?x=-x,putchar('-'):0;short st[50],top=0;
        while(x) st[++top]=x%10,x/=10;
        while(top) putchar(st[top--]+'0');
    }
    inline void read(char&c){c=getchar();while(isspace(c)) c=getchar();}
    inline void write(char c){putchar(c);}
    inline void read(string&s){s.clear();char c;read(c);while(!isspace(c)&&~c) s+=c,c=getchar();}
    inline void write(string s){for(int i=0,len=s.size();i<len;i++) putchar(s[i]);}
    template<typename T>inline void write(T*x){while(*x) putchar(*(x++));}
    template<typename T,typename...T2> inline void read(T&x,T2&...y){read(x),read(y...);}
    template<typename T,typename...T2> inline void write(const T x,const T2...y){write(x),putchar(' '),write(y...),sizeof...(y)==1?putchar('\n'):0;}
}using namespace IO;
template<int mod>struct Modint{
    int z;
    Modint(){z=0;}
    Modint(int x){x%=mod;z=x<0?x+mod:x;}
    Modint(long long x){x%=mod;z=x<0?x+mod:x;}
    Modint(short x){x%=mod;z=x<0?x+mod:x;}
    Modint(char x){x%=mod;z=x<0?x+mod:x;}
    Modint(bool x){x%=mod;z=x<0?x+mod:x;}
    friend Modint operator+(Modint t,Modint t2){Modint ans;ans.z=(t.z+t2.z)%mod;return ans;}
    friend Modint operator*(Modint t,Modint t2){Modint ans;ans.z=1ll*t.z*t2.z%mod;return ans;}
    friend Modint operator-(Modint t,Modint t2){Modint ans;ans.z=(t.z-t2.z)%mod;return ans;}
    Modint operator<<(const int t)const{Modint ans;ans.z=(z<<t)%mod;return ans;}
    Modint operator>>(const int t)const{Modint ans;ans.z=(z>>t)%mod;return ans;}
    Modint&operator+=(const Modint t){z=(z+t.z)%mod;return *this;}
    Modint&operator*=(const Modint t){z=1ll*z*t.z%mod;return *this;}
    Modint&operator-=(const Modint t){z=(z-t.z)%mod;return *this;}
    Modint&operator<<=(const int t){z=(z<<t)%mod;return *this;}
    Modint&operator>>=(const int t){z=(z>>t)%mod;return *this;}
    Modint&operator++(){z++,z%=mod;return *this;}
    Modint&operator--(){z--,z%=mod;return *this;}
    Modint operator++(int){Modint ls=*this;z++,z%=mod;return ls;}
    Modint operator--(int){Modint ls=*this;z--,z%=mod;return ls;}
    friend Modint ksm(Modint a,int b){
        Modint ans=1;
        while(b){if(b&1) ans=ans*a;a=a*a,b>>=1;}
        return ans;
    }
    friend void read(Modint&z){
        int x=0;char c=getchar();bool f=0;
        while(!isdigit(c)) c=='-'?f=1:0,c=getchar();
        while(isdigit(c)) x=(x*10ll+c-'0')%mod,c=getchar();
        f?x=-x:0;
        z.z=x;
    }
    friend void write(Modint x){x.z<0?x.z+=mod:0;write(x.z);}
};
const int mod=10007,maxn=210;
#define M Modint<mod>
int n,son[maxn],sz[maxn];
vector<int>e[maxn];
vector<int>rt;
namespace Part1{
    M f[maxn][maxn],ff[maxn];//f[i][j]:i的子树内选j个点的方案数
    void dp(int u,int fa){
        f[u][1]=1,sz[u]=1;
        for(int v:e[u]){
            if(v==fa) continue;
            dp(v,u);
            for(int i=sz[u];i>=1;i--) for(int j=sz[v];j>=1;j--) f[u][i+j]+=f[u][i]*f[v][j];
            sz[u]+=sz[v];
        }
    }
    void solve(int rt){
        memset(f,0,sizeof(f)),memset(ff,0,sizeof(ff));
        for(int v:e[rt]) dp(v,rt);
        int nw=1;
        M ans=0;
        for(int x=0;x<=n;x++){
            int maxx=(n-x-1)/2;
            nw=1,ff[1]=1;
            for(int v:e[rt]){
                for(int i=nw;i>=1;i--) for(int j=min(sz[v],maxx);j>=1;j--) ff[i+j]+=ff[i]*f[v][j];
                nw+=sz[v];
            }
            ans+=ff[n-x];
            for(int i=1;i<=n;i++) ff[i]=0;
        }
        write(ans);write("\n");
    }
};
namespace Part2{
    M f[maxn][maxn];
    void dp(int u,int fa){
        f[u][1]=1,sz[u]=1;
        for(int v:e[u]){
            if(v==fa) continue;
            dp(v,u);
            for(int i=sz[u];i>=1;i--) for(int j=sz[v];j>=1;j--) f[u][i+j]+=f[u][i]*f[v][j];
            sz[u]+=sz[v];
        }
    }
    void solve(int rt1,int rt2){
        memset(f,0,sizeof(f));
        dp(rt1,rt2),dp(rt2,rt1);
        M ans;
        for(int i=1;i<=n;i++) ans+=f[rt1][i]*f[rt2][i];
        write(ans);write("\n");
    }
}
void get_rt(int u,int fa=0){
    sz[u]=1;son[u]=0;
    for(int v:e[u]){
        if(v==fa) continue;
        get_rt(v,u);
        sz[u]+=sz[v];
        son[u]=max(son[u],sz[v]);
    }
    son[u]=max(son[u],n-sz[u]);
    if(son[u]<son[rt[0]]) rt.clear(),rt.push_back(u);
    else if(son[u]==son[rt[0]]) rt.push_back(u);
}
void solve(){
    for(int i=1;i<=n;i++) e[i].clear();
    read(n);
    for(int i=1;i<n;i++){
        int u,v;read(u,v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    son[0]=n;
    rt.clear();
    rt.push_back(0);
    get_rt(1);
    if(rt.size()==1) Part1::solve(rt[0]);
    else Part2::solve(rt[0],rt[1]);
}
signed main(){
    int T;read(T);
    for(int t=1;t<=T;t++) printf("Case %d: ",t),solve();
    return 0;
}
posted @ 2026-04-13 20:06  Link-Cut_Trees  阅读(20)  评论(0)    收藏  举报