题解 P8935【[JRKSJ R7] 茎】

Link

脑子短路。

题意

你有一棵 n 个点的根节点为 1 的有根树,现在你要对这棵树进行剪枝,每次你可以选择一个还未被剪掉的节点 u 进行操作,然后剪掉 u 的子树所有点(包括 u)。当且仅当你剪掉 1 时,操作停止。

你需要在第 k 次剪枝时对 x 进行操作,而非仅仅将其剪掉,即你不能在第 k 次对其祖先进行操作使其被连带剪掉。

求有多少种不同的操作序列,两个操作序列不同当且仅当长度不同或存在一次操作 i 使得两操作序列在第 i 次时选择的 u 不同。输出答案模 109+7

n500

思路

考虑先算 fx,i 表示 x 子树内进行 i 次操作,不必须删完的方案数。先得到不考虑 x 的方案数,转移为 fx,0=1tsons(x),fx,i+j=fx,ift,j(i+jj)。再考虑 xfx,i=fx,i+fx,i1

x 与其祖先组成的点集为 S,从 1 开始向下倒序删点。我们定义 gx,i 表示考虑到 S 的前 x 位,我们已经钦定了 Sx 前的点的不在 S 中的子树的 i 个操作。这些操作有相对顺序,但还未被放在操作序列中。

我们令 hx,i 表示 x 不在 S 内的子树进行 i 次操作的方案数。hx,0=1tsons(x) and tS,hx,i+j=hx,ift,j(i+jj)

考虑一次转移,当 x 不被操作时,gx,i=gx1,i。注意最后一个点不能不被操作,所以不能复制。当 x 被放在 p 位置时,gx1,p+k 都可以转移到 gx,p。再将 x 不在 S 中的子树的操作加入到 j 中。gx,i+l=gx,i+l+gx,ihx,l(i+ll)

最终的答案为 gx,k1

这时的时间复杂度为 O(n3),考虑优化。我们令 px,i 表示 c0gx,i+c。则上述转移中的 gx,p 可以直接由 px1,p 转移得到,此时时间复杂度优化到 O(n2),即可通过。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff

}
const int N=500+10,mod=1e9+7;
int n,k,p,fac[N],ifac[N],cnt,head[N];
struct Edge{
    int to,nxt;
}a[N<<1];
inline void add(int u,int v){
    cnt++;
    a[cnt].to=v;
    a[cnt].nxt=head[u];
    head[u]=cnt;
}
inline int qpow(int x,int y){
    int res=1;
    while(y){
        if(y&1) res=1ll*res*x%mod;
        x=1ll*x*x%mod;
        y/=2; 
    }
    return res;
}
inline void Prefix(int n){
    fac[0]=1;
    for(int i=1;i<=n;i++)
        fac[i]=1ll*fac[i-1]*i%mod;
    ifac[n]=qpow(fac[n],mod-2);
    for(int i=n;i;i--)
        ifac[i-1]=1ll*ifac[i]*i%mod;
}
inline int C(int n,int m){
    return n<m?0:1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int f[N][N],g[2][N*2],pr[N],tmp[N],fr[N],siz[N],ans;
bool fl[N];
//g[i][j][k] i 及 i 祖先的其它子树内已经选了 k 个 
inline void dfs(int x,int fa){
    f[x][0]=1,fr[x]=fa;
    for(int i=head[x];i;i=a[i].nxt){
        int t=a[i].to;
        if(t==fa) continue;
        dfs(t,x);
        for(int j=siz[x];j>=0;j--)
            for(int k=siz[t];k>=1;k--)
                f[x][j+k]=(f[x][j+k]+1ll*f[x][j]*f[t][k]%mod*C(j+k,k))%mod;
        siz[x]+=siz[t];
    }
    for(int i=siz[x];i>=0;i--)
        f[x][i+1]=(f[x][i+1]+f[x][i])%mod; 
    siz[x]++;
}
inline void oth(int x){//这个节点其它子树 i 次操作的方案数 
    int s=0;
    tmp[0]=1;
    for(int i=1;i<=n;i++)
        tmp[i]=0;
    for(int i=head[x];i;i=a[i].nxt){
        int t=a[i].to;
        if(t==fr[x]||fl[t]) continue;
        for(int j=s;j>=0;j--)
            for(int k=siz[t];k>=1;k--)
                tmp[j+k]=(tmp[j+k]+1ll*tmp[j]*f[t][k]%mod*C(j+k,k))%mod;
        s+=siz[t];
    }
}
vector<int>s;
int main(){
    n=read(),k=read(),p=read();
    Prefix(n);
    for(int i=1;i<n;i++){
        int u=read(),v=read();
        add(u,v),add(v,u);
    }
    dfs(1,0);
    int t=p;
    while(t){
        s.push_back(t);
        fl[t]=1,t=fr[t];
    }
    reverse(s.begin(),s.end());
    oth(s[0]);
    int l=s.size();
    for(int j=0;j<=n;j++)
        g[0][j]=tmp[j];
    for(int i=1;i<s.size();i++){
        int c=i&1;
        oth(s[i]);
        int sz=siz[s[i]];
        if(i!=l-1) sz-=siz[s[i+1]];
        for(int t=0;t<=n;t++)
            g[c][t]=g[!c][t]*(i!=l-1);
        for(int t=n;t>=0;t--)
            pr[t]=(pr[t+1]+g[!c][t])%mod;
        for(int d=n;d>=0;d--)
            g[c][d]=(g[c][d]+pr[d])%mod;
        for(int j=n-1;j>=0;j--)
            for(int d=sz;d;d--)
                g[c][j+d]=(g[c][j+d]+1ll*g[c][j]*tmp[d]%mod*C(j+d,d))%mod;
    }
    write(g[l-1&1][k-1]);
    flush();
}

再见 qwq~

posted @   ffffyc  阅读(9)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示