[NOIP模拟赛]酷子集

37 篇文章 1 订阅
17 篇文章 0 订阅
题目描述
给出整数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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NOI(全国青少年信息学奥林匹克竞模拟的测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟的测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值