• [题解] LuoguP6478 [NOI Online #2 提高组]游戏(民间数据)


    https://www.luogu.com.cn/problem/P6478

    二项式反演

    发现恰好有(k)个非平局的回合数,“恰好”这并不好做,这里套路地设(f_i)表示恰好有(i)个非平局的方案数,(g_i)表示“钦定”了(i)个非平局的然后剩下的(m-i)个回合两人随便选点的方案数。

    考虑(f)(g)有什么关系,对于(g_k)(f_i)(i ge k)),由于是先钦定(k)局,考虑(i)局中钦定了哪(k)局,换句话说(f_i)(g_k)中会被重复计数(inom{i}{k})

    于是

    [g_k=sumlimits_{i=k}^{m} inom{i}{k} f_i ]

    由二项式反演可得

    [f_k=sumlimits_{i=k}^{m} (-1)^{i-k} inom{i}{k} g_i ]

    于是算出(g)后我们就可以(O(m^2))得到答案了。

    考虑树形dp,令(dp(u,i))表示点(u)的子树内,选出(i)组有祖孙关系的点对的方案数。

    只有两种情况

      1. 不选(u)节点
      1. (u)节点

    先来看(1),不选(u)节点的话就类似树上背包那样合并儿子们的信息

    这样我们得到了新的(dp(u,i))

    然后如果选择了点(u),那么加入这个点对过后会让(dp(u,i))(dp(u,i+1))做出贡献(这里的(dp)值都是上面更新好的),具体的会使得(dp(u,i+1))加上(dp(u,i) imes u)子树内的剩下没有被选的点(对手的点)的个数,剩下的点的个数就是(u)子树内对手点的个数(-i)

    注意上面这个东西不要在更新的时候覆盖(dp)值(可以倒序枚举(i)或者再开一个数组)

    然后显然(g_k=dp(1,k) imes (m-k)!)(即钦定(k)组的方案数乘上剩下随便选的方案数)

    这样背包貌似是(O(n^3))的...

    但对于(dp(u,i)),粗略的估计一下(i)的上界就是(siz_u)

    然后按这个上界枚举的话复杂度会到(O(n^2))

    感性的理解就是两个点(u,v)仅会在(lca(u,v))处对复杂度造成贡献。

    (Code:)

    #include <bits/stdc++.h>
    using namespace std;
    #define maxn 5000
    #define N 5555
    #define MOD 998244353
    inline int qpow(int a,int b=MOD-2,int m=MOD)
    {
        int ans=1%m;
        for(;b;b>>=1,a=1ll*a*a%m)
            if(b&1) ans=1ll*ans*a%m;
        return ans;
    }
    inline char nc()
    {
        char ch=getchar(); while(isspace(ch))ch=getchar();
        return ch;
    }
    int fac[N],ifac[N];
    inline int comb(int n,int r)
    {return 1ll*fac[n]*ifac[r]%MOD*ifac[n-r]%MOD;}
    bool bl[N]; int n,m;
    int fst[N<<1],nxt[N<<1],to[N<<1],ec;
    void ade(int u,int v) {to[++ec]=v,nxt[ec]=fst[u],fst[u]=ec;}
    void addedge(int u,int v) {ade(u,v),ade(v,u);}
    #define fedge(i,u) for (int i=fst[u],v=to[i];i;i=nxt[i],v=to[i])
    int siz[N],c[2][N],f[N][N],df[N];
    void dfs(int u,int ff)
    {
        f[u][0]=1,siz[u]=1,c[0][u]=bl[u]==0,c[1][u]=bl[u]==1;
        fedge(_,u) if(v!=ff)
        {
            dfs(v,u);
            for(int i=0;i<=siz[u];i++)
                for(int j=0;j<=siz[v];j++)
                {
                    df[i+j]+=1ll*f[u][i]*f[v][j]%MOD;
                    if(df[i+j]>=MOD) df[i+j]-=MOD;
                }
            siz[u]+=siz[v];
            c[0][u]+=c[0][v],c[1][u]+=c[1][v];
            for(int i=0;i<=siz[u];i++) f[u][i]=df[i];
            for(int i=0;i<=siz[u];i++) df[i]=0;
        }
        int cc=c[!bl[u]][u];
        for(int i=0;i<=cc;i++)
        {
            df[i+1]+=1ll*f[u][i]*(cc-i)%MOD;
            if(df[i+1]>=MOD) df[i+1]-=MOD;
        }
        for(int i=0;i<=siz[u];i++) {f[u][i]+=df[i]; if(f[u][i]>=MOD)f[u][i]-=MOD;}
        for(int i=0;i<=cc;i++) df[i]=df[i+1]=0; 
    }
    int ans[N];
    int main()
    {
        fac[0]=ifac[0]=1;
        for(int i=1;i<=maxn;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=qpow(fac[i]);
        scanf("%d",&n),m=n>>1;
        for(int i=1;i<=n;i++) bl[i]=nc()=='1';
        for(int u,v,i=1;i<n;i++)
            scanf("%d%d",&u,&v),addedge(u,v);
        dfs(1,0);
        for(int i=0;i<=m;i++)
        {
            ans[i]=0;
            for(int j=i;j<=m;j++)
            {
                ans[i]+=1ll*comb(j,i)*(((j-i)&1)?MOD-1:1)%MOD*f[1][j]%MOD*fac[m-j]%MOD;
                if(ans[i]>=MOD) ans[i]-=MOD;
            }
            printf("%d
    ",ans[i]);
        }
        return 0;
    }
    
  • 相关阅读:
    16 继续讲C#中的条件执行。if...else if...else
    15 C#中的条件执行,if else
    14 C#编程中的逻辑运算
    13 继续C#中的方法,带返回值的方法介绍
    12 C#中的方法
    在使用实体框架(Entity Framework)的应用中加入审计信息(Audit trail)跟踪数据的变动
    11 在C#中写文件
    10 在C#中读取文件
    9 在C#控制台程序(console)中让用户输入
    大数据组件图谱
  • 原文地址:https://www.cnblogs.com/wxq1229/p/12863220.html
Copyright © 2020-2023  润新知