#NOIP模拟赛#Cool子集(Dp + 状压)





用一个int S的二进制来表示当前这一个集合中包含哪些数字(0~9)
定义Bit[S]表示当前状态S中有多少个数字(即二进制中有多少个1)
定义Dp[S]表示当这个集合中只有一个整数, 这个整数不超过N,且包含数字的状态为S时,这个集合最多可能有多少种方案。
对于Dp[S]的处理:
当Bit[S] > N的位数:Dp[S] = 0;
当Bit[S] < N的位数:Dp[S] = Fac[Bit[S]];(Fac:阶乘)
当S中有数字‘0’时,Dp[S] = Fac[Bit[S]] - Fac[Bit[S] - 1](此时减去令最高位为0时可能的方案数)
当Bit[S] == N的位数:计算出小于等于N的全排列数


定义f[i][j][S]表示一个集合中包含i个数字,它们组成j个整数,数字包含状态为S时的方案数
f[i][j][S] = Σf[k][j - 1][TS] * Dp[S ^ TS](TS是S的子状态) 2 <= j <= i
f[i][1][S] = Dp[S];


最后答案是Σf[i][j][S] (1 <= j <= i <= 10,0 <= S <= (1 << 10) - 1)


Code:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

const int Maxn = 10;
const int Maxs = 1 << 10;
const int MOD = 1e9 + 7;

int N, cnt, SALL;
int num[Maxn + 5], Bit[Maxs + 5], Dp[Maxs + 5];
int Fac[Maxn + 5], Inv[Maxn + 5];
int f[Maxn + 5][Maxn + 5][Maxs + 5];

bool getint(int & num){
    char c; int flg = 1;    num = 0;
    while((c = getchar()) < '0' || c > '9'){
        if(c == '-')    flg = -1;
        if(c == -1) return 0;
    }
    while(c >= '0' && c <= '9'){
        num = num * 10 + c - 48;
        if((c = getchar()) == -1)   return 0;
    }
    num *= flg;
    return 1;
}

void Pre(){
    Fac[0] = Fac[1] = Inv[0] = Inv[1] = 1;
    for(int i = 2; i <= 10; ++ i)    Fac[i] = 1ll*Fac[i - 1]*i%MOD, Inv[i] = 1ll*(MOD-MOD/i)*Inv[MOD%i]%MOD;
    for(int i = 2; i <= 10; ++ i)    Inv[i] = 1ll*Inv[i]*Inv[i-1]%MOD;
}

int main(){
    //freopen("data3.in", "r", stdin);
    freopen("cool.in", "r", stdin);
    freopen("cool.out", "w", stdout);
    getint(N);
    Pre();
    int t = N;
    while(t){
        num[++ cnt] = t % 10;
        t /= 10;
    }
    SALL = 1 << 10;
    for(int S = 0; S < SALL; ++ S){
        Bit[S] = Bit[S >> 1] + (S & 1);
        if(Bit[S] > cnt)    Dp[S] = 0;
        else if(Bit[S] < cnt){
            Dp[S] = Fac[Bit[S]];
            if(S & 1)   Dp[S] -= Fac[Bit[S] - 1];
        }
        else {
            Dp[S] = 0;
            int TS = S;
            bool flg = 1;
            for(int i = cnt; i >= 1; -- i){
                for(int j = (i == cnt); j < num[i]; ++ j)   if(TS & (1 << j))
                    Dp[S] += Fac[Bit[TS] - 1];
                if(! (TS & (1 << num[i]))){
                    flg = 0;
                    break;
                }
                else TS -= 1 << num[i];
            }
            Dp[S] += flg;
        }
    }
    f[0][0][0] = 1;
    for(int i = 1; i <= 10; ++ i)
        for(int j = 1; j <= i; ++ j)
            for(int S = 1; S < SALL; ++ S)  if(Bit[S] == i){
                if(j == 1)  f[i][j][S] = Dp[S];
                else{
                    for(int k = 1; k < i; ++ k)
                        for(int TS = S & (S - 1); TS; TS = (TS - 1) & S)    if(Bit[TS] == k)
                            f[i][j][S] = (f[i][j][S] + 1ll * f[k][j - 1][TS] * Dp[S ^ TS]) % MOD;
                }
            }
    int Ans = 0;
    for(int i = 1; i <= 10; ++ i)
        for(int j = 1; j <= i; ++ j)
            for(int S = 0; S < SALL; ++ S)
                Ans = (Ans + 1ll * f[i][j][S] * Inv[j]) % MOD;
    printf("%d\n", Ans);
    return 0;
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值