[NOIP模拟赛]酷子集

来源:互联网 发布:php mysql 存储过程 编辑:程序博客网 时间:2024/05/23 01:13
题目描述
给出整数N, 则集合S包含整数1, 2, 3, ... , N。考虑S的某个非空子集T,把子集T的所有元素都写下来,如果使用0-9中每个数字的次数都没有超过1次(允许是0次),则把子集T称为酷子集。

例如,子集{12,345,67890} 和 {47,109}都是酷子集,而 {147,342}不是,因为数字4使用了2次。同理,{404}也不是酷子集。


输入格式

第1行:1个整数N(1≤N≤10^9),表示集合S的元素个数。


输出格式

第1行:1个整数,表示答案,答案模1e9+7。


输入样例

10


输出样例

767


样例说明
N=10的所有非空子集有2^10-1=1023个。不是酷子集的情况是同时包含1和10,这类子集一共有2^8 =256个,所以答案是1023-256=767。


题解 by zjx

对于所有满足条件的集合,每个数字最多出现一次,而总共有10个数字,可以考虑枚举这10个数字的状态。

设f[s]表示所用的数字集合是s且集合中所有数均不超过n的方案总数。考虑s的一个子集v,用v组成一个数,s-v组成其余的数。

记n有p位:

1.如果|v|<p,那么v的任何一个排列都是满足条件的(当然0不能放在首位,需要特判);

2.如果|v|=p,那么我们一位一位考虑。

①对于某一位k,如果这一位放了一个小于n这一位的数(当然第一位不能放0),之后的数可以任意排列,方案数为(p-k)!;

②否则我们在这里放置和原数这一位一样的数(前提是这些数中有),继续处理k+1位;

③特别的,如果v完全等于n,答案需要加上1。算出v的方案数后,与f[s-v]相乘即为答案。

3.如果|v|>p,直接赋0。

最后把所有的f[1]~f[2^10-1]加起来就是答案了


#include<cstdio>const int MOD=1e9+7;const int N=15;const int M=(1<<10)+5;int n, ncnt, note[10];void Prep_digit() {scanf( "%d", &n );while(n) note[++ncnt]=n%10, n/=10;}int fac[N], inv[N];void Prep_FI() {//预处理出阶乘与阶乘的逆元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*inv[MOD%i]*( MOD-MOD/i )%MOD;}for( int i=2; i<=10; i++ ) inv[i]=1ll*inv[i]*inv[i-1]%MOD;}int f[M], bcnt[M], jud, scnt;void Prep_situ() {scnt=(1<<10);for( int s=0; s<scnt; s++ ) {//状态压缩, 用s的二进制数表示状态, 当前位是1就说明包含这个数bcnt[s]=bcnt[s>>1]+(s&1);//记录当前集合包含几个数if( bcnt[s]>ncnt ) f[s]=0;//比n的位数多else if( bcnt[s]<ncnt ) {//比n的位数少f[s]=fac[ bcnt[s] ];if( s&1 ) f[s]-=fac[ bcnt[s]-1 ];//减去第0位的情况数}else {//一样多f[ jud=s ]=0;bool ok=1;for( int i=ncnt; i; i-- ) {for( int j=(i==ncnt); j<note[i]; j++ )//首位不能为0if( jud&(1<<j) ) f[s]+=fac[ bcnt[jud]-1 ];if( !( jud&(1<<note[i]) ) ) { ok=0; break; }else jud-=(1<<note[i]);}f[s]+=ok;}}}int dp[N][N][M], ans;void Dp() {dp[0][0][0]=1;for( int i=1; i<=10; i++ )for( int j=1; j<=i; j++ )for( int s=1; s<scnt; s++ )if( bcnt[s]==i ) {if( j==1 ) dp[i][j][s]=f[s];else for( int k=1; k<i; k++ )for( int jud=(s-1)&s; jud; jud=(jud-1)&s ) if( bcnt[jud]==k )( dp[i][j][s]+=1ll*dp[k][j-1][jud]*f[s^jud]%MOD )%=MOD;}}void Print() {for( int i=1; i<=10; i++ )for( int j=1; j<=i; j++ )for( int s=0; s<scnt; s++ )( ans+=1ll*dp[i][j][s]*inv[j]%MOD )%=MOD;( ans+=MOD )%=MOD;printf( "%d\n", ans );}int main() {Prep_digit();Prep_FI();Prep_situ();Dp();Print();return 0;}

原创粉丝点击