题解 P8935【[JRKSJ R7] 茎】

$\text{Link}$

脑子短路。

题意

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

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

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

$n\le 500$。

思路

考虑先算 $f_{x,i}$ 表示 $x$ 子树内进行 $i$ 次操作,不必须删完的方案数。先得到不考虑 $x$ 的方案数,转移为 $f_{x,0}=1$,$\displaystyle\forall t\in \text{sons}(x),f_{x,i+j}=f_{x,i}f_{t,j}\binom{i+j}{j}$。再考虑 $x$,$f_{x,i}=f_{x,i}+f_{x,i-1}$。

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

我们令 $h_{x,i}$ 表示 $x$ 不在 $S$ 内的子树进行 $i$ 次操作的方案数。$h_{x,0}=1$,$\displaystyle\forall t\in \text{sons}(x)\text{ and }t\notin S,h_{x,i+j}=h_{x,i}f_{t,j}\binom{i+j}{j}$。

考虑一次转移,当 $x$ 不被操作时,$g_{x,i}=g_{x-1,i}$。注意最后一个点不能不被操作,所以不能复制。当 $x$ 被放在 $p$ 位置时,$g_{x-1,p+k}$ 都可以转移到 $g_{x,p}$。再将 $x$ 不在 $S$ 中的子树的操作加入到 $j$ 中。$\displaystyle g_{x,i+l}=g_{x,i+l}+g_{x,i}h_{x,l}\binom{i+l}{l}$。

最终的答案为 $g_{x,k-1}$。

这时的时间复杂度为 $O(n^3)$,考虑优化。我们令 $p_{x,i}$ 表示 $\displaystyle \sum_{c\ge 0}g_{x,i+c}$。则上述转移中的 $g_{x,p}$ 可以直接由 $p_{x-1,p}$ 转移得到,此时时间复杂度优化到 $O(n^2)$,即可通过。

代码:

#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 @ 2022-12-23 08:01  ffffyc  阅读(24)  评论(0)    收藏  举报  来源