您的位置:首页 > 其它

[NOIP模拟][数位DP]魔法数字

2017-06-09 20:30 357 查看
ASDFZ-NOIP2016模拟

题目



分析

题解分析:

设p = LCM(q1, q2, q3 … qn),则任意数 s = t * p + r (r < p),很容易推导出:s % qi = r % qi,也就是说只要我们知道一个数模 p 的余数,就能得到它模q1, q2, q3 … qn 的余数。 对于本题而言,p = LCM(1, 2, 3,4, 5, 6, 7, 8, 9) = 2520。我们先把[L, R]的问题转化为[0, R]的问题与[0, L - 1]问题的差值。而[0, x]的问题容易用数位DP解决,我们只需从高位到低位,状态记录当前在哪一位、前面的位数是否与 x相等、9 个数分别是否出现、模 p 的值即可。时间复杂度 O(10 * 2^9 * logR / log10 * 2520)。 (不了解该性质也可以直接应用最小表示法解决) 一个可行的优化:当我们知道尚未加入最后一位前的数模1、 2、3、4、6、7、9 的值时,是否为5 或8的倍数只和最后一位有关,如此可以把状态中的2520 压到252。

自己的分析:

这道题的暴力还是很好拿的。正解的话,首先那个所谓的可行优化貌似是必须的,因为不这样过不了所有点。正解的记忆化搜索让它的复杂度大大下降。而且个人认为8应该是后三位有关系,因为2520>1000,所以不影响。详解见代码注释。

附代码

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<queue>
#include<iomanip>
#include<set>
#include<cctype>
#include<ctime>
#include<algorithm>
using  namespace std;

int k;
long long l,r,ans,bit[20],dp[20][512][252][2];
int len,mark[20];

inline<
c118
/span> long long readlong()
{
char ch;long long i=0;int f=1;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-')
{
ch=getchar();
f=-1;
}
for(;ch>='0'&&ch<='9';ch=getchar())
i=(i<<3)+(i<<1)+ch-'0';
return i*f;
}

inline void solve(long long x)//计算出这个数有多少位,分别每位是什么
{
memset(mark,0,sizeof(mark));
memset(dp,-1,sizeof(dp));
for(int i=1;i<20;i++)
if(x<bit[i])
{
len=i;
break;
}
for(int i=len;i>=1;i--)
{
mark[i]=x%10;
x=x/10;
}
}

inline long long dfs(int pos,int vis,int mod,bool limit)//pos是此时的长度,mod就是当前数除以252的余数
{                                                       // limit判断是否达到了最高位
if(pos>len)//此时实际上是位数刚好相等,判断有无1~9数字,并且可以整除
{
int cnt=0;
if(vis&1) cnt++;
if(vis&(1<<1)&&mod%2==0) cnt++;
if(vis&(1<<2)&&mod%3==0) cnt++;
if(vis&(1<<3)&&mod%4==0) cnt++;
if(vis&(1<<4)&&mod%5==0) cnt++;
if(vis&(1<<5)&&mod%6==0) cnt++;
if(vis&(1<<6)&&mod%7==0) cnt++;
if(vis&(1<<7)&&mod%8==0) cnt++;
if(vis&(1<<8)&&mod%9==0) cnt++;
if(cnt>=k) return 1;
else return 0;
}
mod%=252;
if(dp[pos][vis][mod][limit]!=-1)//记忆化搜索,如果以及搜过,则不用再搜。
return dp[pos][vis][mod][limit];
int end;
if(limit)//只有达到最高位,才会受到原数每一位的限制
end=mark[pos];
else
end=9;
long long res=0;
for(int i=0;i<=end;i++)//要枚举0,因为那些小的数,就是前面填充的0
{
if(i!=0)
res+=dfs(pos+1,vis|(1<<i-1),mod*10+i,limit&&(i==end));//vis相当于是以二进制存了当前数含有哪些数字
else                                    //如果一个数是5或8的倍数,那么除最后一位外的前几位除以252的余数
res+=dfs(pos+1,vis,mod*10+i,limit&&(i==end));   //再乘以10加上最后一位,那么它也必然是5或8的倍数
}                                                   //乘10相当于模的是2520,而5和8的倍数与后三位有关
dp[pos][vis][mod][limit]=res;
return dp[pos][vis][mod][limit];
}

int main()
{
//freopen("magic.in","r",stdin);
//freopen("magic.out","w",stdout);

k=readlong();l=readlong();r=readlong();
bit[0]=1;
for(int i=1;i<20;i++)
bit[i]=bit[i-1]*10;

solve(r);
ans=dfs(1,0,0,true);
solve(l-1);
ans-=dfs(1,0,0,true);
printf("%I64d",ans);

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: