题目描述
给出整数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++ )//首位不能为0 if( 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; }