其他分享
首页 > 其他分享> > 题解 nflsoj489 【六校联合训练 CSP #15】小D与随机

题解 nflsoj489 【六校联合训练 CSP #15】小D与随机

作者:互联网

题目链接

考虑枚举好点的集合。此时要考虑的问题是如何填入\(1\sim n\)这些数使得恰好我们枚举到的这些点是好点,即:求出有多少种合法的填数方案。

\(1\)号点一定是好点。那么除\(1\)号点外每个点的祖先中必有至少一个好点。定义\(u\)的祖先中离\(u\)最近的好点,为\(u\)的祖先好点\((2\leq u\leq n)\)。

对每个点:

那么对于任意一条边\(a\rightarrow b\),它代表的含义是一种限制:\(val_a<val_b\)。显然,一种填数方案合法,当且仅当满足所有这些限制。

这些边的方向有上有下,让我们非常头疼。如果只有一些第一类限制,问题相当于给定一棵树,求有多少种填数方案使得祖先的权值总是大于后代。这是非常简单的经典问题(其答案是\(n!\prod_u\frac{1}{sz_u}\))。

所以我们可以考虑对第二类边做容斥。即:我们强行把一些坏点向其祖先好点连的边的方向反过来,求出此时的填数方案数,再乘以\((-1)^{\text{被反向的坏点个数}}\),加入答案。当然,我们不能暴力枚举使哪些坏点反向,否则总复杂度变为\(O(3^n\times n)\),且没什么优化前途。

考虑DP。设\(dp[u][i]\)表示考虑了\(u\)的子树,共选出\(i\)个有限制的点(既包括所有原本就是好点的点,也包括一些被容斥强行反向的坏点)的填数方案数。注意:
这里的填数方案只考虑了这\(i\)个有限制的点,对于无限制的坏点我们统计答案时再计算。并且对于这\(i\)个有限制的点,我们只考虑它们填的数的相对大小关系,这样方便转移。

把\(u\)的子树的方案合并起来,就是做一个简单的树上背包。也就是大家所熟知的那个看起来是\(O(n^3)\)实际上是\(O(n^2)\)的算法。这里不再赘述。

设我们把\(u\)的所有儿子的DP值合并得到\(g\),即:在\(u\)的所有儿子子树内,选出\(i\)个有限制的点的方案数为\(g[i]\)。

然后转移,考虑\(u\)。

考虑统计在当前好点集合下的方案数。

设好点集合大小为\(c\)。枚举选出了\(i\)个有限制的点。则方案数为:

\[ x=\sum_{i=c}^{n}dp[1][i]\times{n\choose i}\times(n-i)! \]

我们把这个方案数贡献到答案里,即\(ans+=x\times k^c\)。

至此,我们实现了\(O(2^n\times n^2)\)的复杂度并喜提\(0\)分。但其实我们离正解只有一步之遥。

我们完全可以不枚举好点集合,在DP转移时讨论\(u\)是不是好点即可。至于\(k^c\)这个东西,我们在转移时,如果认为\(u\)是好点,就把它的DP值乘以一个\(k\)。即:\(dp[u][i]=k\times g[i-1]\)。

时间复杂度\(O(n^2)\)。

参考代码:

//problem:nflsoj489
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
    if(S==T){
        T=(S=buf)+fread(buf,1,MAXN,stdin);
        if(S==T)return EOF;
    }
    return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
    #define getchar Fread::getchar
#endif
inline int read(){
    int f=1,x=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline ll readll(){
    ll f=1,x=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
/*  ------  by:duyi  ------  */ // dysyn1314
const int MAXN=5000,MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int n,K,fac[MAXN+5],invf[MAXN+5],sz[MAXN+5],dp[MAXN+5][MAXN+5],tmp[MAXN+5];
vector<int>G[MAXN+5];
inline int comb(int n,int k){
    if(n<k)return 0;
    else return (ll)fac[n]*invf[k]%MOD*invf[n-k]%MOD;
}
void dfs(int u,int fa){
    sz[u]=1;
    dp[u][0]=1;
    for(int i=0;i<(int)G[u].size();++i){
        int v=G[u][i];
        if(v==fa)continue;
        dfs(v,u);
        for(int j=0;j<=sz[v]+sz[u];++j)tmp[j]=0;
        for(int j=0;j<=sz[v];++j){
            for(int k=0;k<=sz[u];++k){
                add(tmp[j+k],(ll)dp[v][j]*dp[u][k]%MOD*comb(j+k,j)%MOD);
            }
        }
        sz[u]+=sz[v];
        for(int j=0;j<=sz[u];++j)dp[u][j]=tmp[j];
    }
    for(int i=0;i<=sz[u];++i)tmp[i]=dp[u][i];
    for(int i=1;i<=sz[u];++i){
        dp[u][i]=0;
        add(dp[u][i],(ll)K*tmp[i-1]%MOD);//好点
        if(u!=1){
            sub(dp[u][i],(ll)i*tmp[i-1]%MOD);//假好点(边被反向了的坏点)
            add(dp[u][i],tmp[i]);//u被孤立了
        }
    }
}
int main() {
    n=read();K=read();
    fac[0]=1;for(int i=1;i<=n;++i)fac[i]=(ll)fac[i-1]*i%MOD;
    invf[n]=pow_mod(fac[n],MOD-2);
    for(int i=n-1;i>=0;--i)invf[i]=(ll)invf[i+1]*(i+1)%MOD;
    for(int i=1,u,v;i<n;++i)u=read(),v=read(),G[u].pb(v),G[v].pb(u);
    dfs(1,0);
    int ans=0;
    for(int i=n,s=1;i>=1;s=(ll)s*i%MOD,--i){
        add(ans,(ll)dp[1][i]*s%MOD);
    }
    cout<<ans<<endl;
    return 0;
}

标签:15,int,题解,好点,times,祖先,MAXN,六校,dp
来源: https://www.cnblogs.com/dysyn1314/p/12371758.html