P9131 [USACO23FEB] Problem Setting P 题解

link

看数据范围很容易就会往状压方面想。容易想到把每个人对题的评价给状压,但这样是肯定不行的。

我们考虑反着定义状态,把每个题对应的困难评价状压下来。定义状态 \(dp_i\) 表示以评价状态 \(i\) 为结尾时的方案数。如果一个问题的状态是另一个状态的真子集,那么这个问题一定在另一个问题前面。而状态相同的子集则可以随便排列,设个数为 \(cnt\),那么方案数为 \(\sum\limits_{i=0}^{cnt}A_{cnt}^{i}\)。更新 \(dp_i\) 时,我们先将 \(i\) 的真子集的方案数累加起来,再乘以状态为 \(i\) 的问题的排列方案数。

我们考虑优化,看到统计子集容易想到 SOS dp,我们也类似地定义状态 \(f_{i,j}\) 表示只考虑前 \(j\) 位的方案数。通过 \(f\) 就能够快速算出 \(dp_i\) 的值,初始化 \(f_{i,0}=dp_i\) 再更新 \(f\) 的值。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+5,M=22;
const ll mod=1e9+7;
int n,m,msk[N];ll f[N],dp[1<<M][M],cnt[1<<M],ans;
ll qpow(ll a,ll b){
    ll res=1;
    while(b){
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    ll t1=1,t2=1;
    for(int i=1;i<=n;i++){//预处理 A 的和
        t1=t1*i%mod;
        f[i]=t1*t2%mod;
        t2=(t2+qpow(t1,mod-2))%mod;
    }
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++){
            char c;cin>>c;
            if(c=='H')msk[j]|=(1<<(i-1));
        }
    for(int i=1;i<=n;i++)cnt[msk[i]]++;
    for(int i=0;i<(1<<m);i++){
        ll res=1;
        for(int j=1;j<=m;j++)
            if(i&(1<<(j-1)))res=(res+dp[i^(1<<(j-1))][j-1])%mod;
        dp[i][0]=res*f[cnt[i]]%mod;
        ans=(ans+dp[i][0])%mod;
        for(int j=1;j<=m;j++){
            dp[i][j]=dp[i][j-1];
            if(i&(1<<(j-1)))dp[i][j]=(dp[i][j]+dp[i^(1<<(j-1))][j-1])%mod;
        }
    }
    cout<<ans;
    return 0;
}
posted @ 2026-08-31 18:36  cqbzcjh  阅读(3)  评论(0)    收藏  举报