由于这道题我惊人地做出了正解(虽然考试时有小错误只有90分),所以我打算先抛掉模拟赛三day2 third,来写这道题了。。。
很容易发现n的范围很大,O(n)都是无法承受的,所以我们需要换个想法。很容易发现,所有满足条件的集合,每个数字最多出现一次,而总共有10个数字,我们可以考虑枚举这10个数字的状态。
设f[s]表示所用的数字集合是s且集合中所有数均不超过n的方案总数,考虑s的一个子集v,用v组成一个数,s-v组成其余的数。记n有p位,如果|v|<p,那么v的任何一个排列都是满足条件的(当然0不能放在首位,需要特判)。
如果|v|=p,那么我们一位一位考虑。对于某一位k,如果这一位放了一个小于n这一位的数(当然第一位不能放0),之后的数可以任意排列,方案数为(p-k)!,否则我们在这里放置和原数这一位一样的数(前提是这些数中有),继续处理k+1位。
但是很容易发现这样会有重复计算的,例如s={1,2,3,4},会把12,34和34,12算重,所以我们在枚举子集时加上一个限制,v必须包含s的一个特定元素(为了方便用lowbit(s)),这样计算时所有的v都不完全相同,并且包含了所有的情况。
这样做,最后把所有的f[1]到f[2^10-1]加起来就是答案了,时间复杂度O(2^20*log10(n)+1)
你们爱的代码如下:
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define M 1250
#define mod 1000000007
int n,p,all[15];
int bit[M],fac[15];
int ksm[15]={1,2,4,8,16,32,64,128,256,512,1024};
int num[M][15];
int ok[M][15][2];
//每一个状态对于n的每一位是否有对应数字
//以及严格小于这个数字的有多少个
int f[M];//合法方案数
int main()
{
scanf("%d",&n);
p=int(log10(n))+1;//计算n的位数
int e=n;
for(int i=1;i<=p;i++)//处理n每一位上的数
{
all[p-i+1]=e%10;
e/=10;
}
if(n<=9)//特殊情况
{
printf("%d",ksm[n]-1);
return 0;
}
fac[0]=1;
for(int i=1;i<=10;i++)//算阶乘
fac[i]=fac[i-1]*i;
for(int i=1;i<ksm[10];i++)
//预处理状态中包含的元素和包含元素个数
{
for(int j=0;j<10;j++)
{
if(i&(1<<j))
{
num[i][++num[i][0]]=j;
bit[i]++;
}
}
}
for(int i=1;i<ksm[10];i++)
//预处理状态中小于n第k位的元素有几个,是否有恰等于这一位的元素
{
for(int j=1;j<=p;j++)
{
for(int k=1;k<=num[i][0];k++)
{
if(num[i][k]<all[j])
ok[i][j][1]++;
if(num[i][k]==all[j])
ok[i][j][0]=1;
}
}
}
f[0]=1;//初始状态
for(int i=1;i<ksm[10];i++)
{
for(int j=i;j>0;j=(j-1)&i)
{
if(j&(i&-i))
//避免重复计算,固定使用某一个数
{
int m=bit[j];
if(m<p)//位数小于p直接排列
{
if(j&1)//首位不放0
f[i]=(f[i]+1ll
*f[i-j]*(fac[m]-fac[m-1])%mod)%mod;
else
f[i]=(f[i]+1ll
*f[i-j]*fac[m]%mod)%mod;
}
if(m==p)
{
int o=j;
for(int k=1;k<=p;k++)//依次比较每一位
{
if(k==1&&(o&1))//首位不放0
{
f[i]=(f[i]+
1ll*f[i-j]
*(ok[o][k][1]-1)%mod
*fac[p-k]%mod)%mod;
}
else
{
f[i]=(f[i]+
1ll*f[i-j]
*ok[o][k][1]%mod
*fac[p-k]%mod)%mod;
}
if(!ok[o][k][0])
break;
if(k==p)//如果可以完全构成n,方案加1
f[i]=(f[i]+f[i-j])%mod;
o-=ksm[all[k]];
}
}
}
}
}
int ans=0;
for(int i=1;i<ksm[10];i++)
ans=(ans+f[i])%mod;
printf("%d",ans);
}