1.不要62:
链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[20];
ll dp[20][10];//位,前一位的值
ll dfs(ll pos,ll pre,bool limit)
{
if(pos == -1) return 1;
if(!limit && dp[pos][pre] != -1) return dp[pos][pre];
ll up = limit ? a[pos] : 9;
ll tmp = 0;
for(ll i = 0;i <= up;i++)
{
if((pre==6&&i==2)||(i==4))//i不可以等于4,当一位等于6时,该位不能为2
continue;
tmp += dfs(pos-1,i,limit && i == a[pos]);
}
if(!limit) dp[pos][pre] = tmp;
return tmp;
}
ll solve(ll x)
{
ll pos = 0;
while(x)
{
a[pos++] = x%10;
x /= 10;
}
return dfs(pos-1,0,true);
}
int main()
{
ll le,ri;
memset(dp,-1,sizeof dp);
while(~scanf("%lld%lld",&le,&ri) && le + ri)
{
printf("%lld\n",solve(ri) - solve(le - 1));
}
return 0;
}
2.含有49
链接:http://acm.hdu.edu.cn/showproblem.php?pid=3555
ac:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;
ll a[20];
ll dp[20][11];
ll dfs(ll pos,ll pre,bool limit)
{
if(pos == -1) return 1;
if(!limit && dp[pos][pre] != -1) return dp[pos][pre];
ll up = limit ? a[pos] : 9;
ll tmp = 0;
for(ll i = 0;i <= up;i++)
{
if(pre == 4 && i == 9)continue;
tmp += dfs(pos-1,i,limit && i == a[pos]);
}
if(!limit) dp[pos][pre] = tmp;
return tmp;
}
ll solve(ll x)
{
ll pos = 0;
ll t=x;
while(x)//把数位都分解出来
{
a[pos++] = x%10;
x /= 10;
}
return t-dfs(pos-1,0,true);
}
int main()
{
ll n;
int t;
scanf("%d",&t);
memset(dp,-1,sizeof(dp));
while(t--)
{
scanf("%lld",&n);
printf("%lld\n",solve(n)-solve(0));
}
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/221/G
定义一个序列a:7,77,777......,7777777(数字全为7的正整数,且长度可以无限大)
clearlove7需要从含有7的意志的数里获得力量,如果一个整数能被序列a中的任意一个数字整除,并且其数位之和为序列a中任意一个数字的倍数,那么这个数字就含有7的意志,现在给你一个范围[n,m],问这个范围里有多少个数字含有7的意志。
解析:
要求各位相加和是是7的倍数,该数是7的倍数
ac:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[20];
ll dp[20][10][10];//位,和,数位和
ll dfs(ll pos,ll sum,ll add,bool limit)//位,和,数位和
{
if(pos == -1){
return sum%7==0&&add%7==0;//判断是否add%7,sum%7都为0,注意0也会被算进去,但在这题里,是从1开始的
}
if(!limit && dp[pos][sum][add] != -1) return dp[pos][sum][add];
ll up = limit ? a[pos] : 9;
ll tmp = 0;
for(ll i = 0;i <= up;i++)
{
tmp += dfs(pos-1,(sum*10+i)%7,(add+i)%7,limit && i == a[pos]);
}
if(!limit) dp[pos][sum][add] = tmp;
return tmp;
}
ll solve(ll x)
{
ll pos = 0;
while(x)
{
a[pos++] = x%10;
x /= 10;
}
return dfs(pos-1,0,0,true);
}
int main()
{
ll le,ri;
memset(dp,-1,sizeof dp);
while(~scanf("%lld%lld",&le,&ri) && le + ri)
{
printf("%lld\n",solve(ri) - solve(le - 1));
}
return 0;
}
B-number:http://acm.hdu.edu.cn/showproblem.php?pid=3652
求1~n中数位包含13,且%13==0的数的数目
数目dp,用sign标记上一位是否为1,前面是否有13等
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[25];
ll dp[25][15][3];//位,和,sign
ll dfs(ll pos,ll sum,int sign,bool limit)
{
if(pos == -1){
return sum%13==0&&sign==2;
}
if(!limit && dp[pos][sum][sign] != -1) return dp[pos][sum][sign];
ll up = limit ? a[pos] : 9;
ll tmp = 0;
for(ll i = 0;i <= up;i++)
{
int csign=sign;
if(sign==0&&i==1)
csign=1;
else if(sign==1){
if(i==3)
csign=2;
else if(i!=1)
csign=0;
}
tmp += dfs(pos-1,(sum*10+i)%13,csign,limit && i == a[pos]);
}
if(!limit) dp[pos][sum][sign] = tmp;
return tmp;
}
ll solve(ll x)
{
ll pos = 0;
while(x)
{
a[pos++] = x%10;
x /= 10;
}
return dfs(pos-1,0,0,true);
}
int main()
{
ll n;
memset(dp,-1,sizeof dp);
while(~scanf("%lld",&n))
{
printf("%lld\n",solve(n));
}
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/190/D
解析:
基础的数位dp,这里要r是多少,我们二分r,计算(1,r)的数目,求最小的r使得(1,r)的个数为n
ac:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,dp[25][20][2];
int a[25];
ll dfs(ll pos,ll sum,ll sign,ll limit)
{
if(pos==-1)
return sign==1||sum%7==0;
if(!limit&&dp[pos][sum][sign]!=-1)
return dp[pos][sum][sign];
ll ans=0;
int en=limit?a[pos]:9;
for(int i=0;i<=en;i++)
{
int csign=sign;
if(i==7)
csign=1;
ans+=dfs(pos-1,(sum*10+i)%7,csign,limit&&i==en);
}
if(!limit)
dp[pos][sum][sign]=ans;
return ans;
}
ll solve(ll x)
{
int pos=0;
while(x)
{
a[pos++]=x%10;
x/=10;
}
ll ans=dfs(pos-1,0,0,1);
return ans;
}
int main()
{
ll n,m;
memset(dp,-1,sizeof(dp));
scanf("%lld%lld",&m,&n);
ll l=n,r=1e18;
ll cc=solve(m);
ll ans=1e18;
while(l<=r)
{
ll mid=(l+r)/2;
ll dd=solve(mid)-cc;
if(dd<n){
l=mid+1;
}
else{
r=mid-1;
ans=min(ans,mid);
}
}
printf("%lld\n",ans);
return 0;
}
https://vjudge.net/problem/CodeForces-55D
题意:
给两个数a,b
求a到b之间的能整除该数各位的数的个数
如果一个数可以整除他的各位数的最小公倍数,呢么就能整除各位数
dp[][][]代表:位,值,lcm,lcm可能非常大,呢么需要20*2520*2520空间,
但是lcm的种类非常少,1~9任意组合的lcm总共就48种.所以不需要开2520空间
值的话只要%2520,lcm可以离散化操作
ac:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[22];
int hash1[2522];
ll dp[22][2522][50];//位,和,lcm,本来至少要开20*2520*2520空间,但是lcm只有48种
ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); }
ll dfs(int pos,int sum,int lcm,int limit)//数位,和,各数位最小公倍数
{
if(pos==-1)return sum%lcm==0;//如果和能整除个数位的最小公倍数
if(!limit&&dp[pos][sum][hash1[lcm]]!=-1)
return dp[pos][sum][hash1[lcm]];
ll ans=0;
int en=limit?a[pos]:9;
for(int i=0;i<=en;i++)
{
ans+=dfs(pos-1,(sum*10+i)%2520,i==0?lcm:lcm*i/gcd(lcm,i),limit&&i==en);//注意gcd(lcm,i)可能为0,直接/可能会崩溃
}
if(!limit)
dp[pos][sum][hash1[lcm]]=ans;
return ans;
}
void init()//离散化
{
int num=0;
for(int i=1;i<=2520;i++){//为2520的因子,这些数是1~9任意组合的最小公倍数的所以可能
if(2520%i==0)
hash1[i]=num++;
}
}
ll solve(ll x)
{
int pos=0;
while(x){
a[pos++]=x%10;
x/=10;
}
return dfs(pos-1,0,1,1);
}
int main()
{
init();
ll n,m,t;
memset(dp,-1,sizeof(dp));
scanf("%I64d",&t);
while(t--)
{
scanf("%I64d%I64d",&n,&m);
printf("%I64d\n",solve(m)-solve(n-1));
}
return 0;
}
http://www.51nod.com/Challenge/Problem.html#problemId=1009
题意:
给定一个十进制正整数N,写下从1开始,到N的所有正数,计算出其中出现所有1的个数.
例如:n = 12,包含了5个1。1,10,12共包含3个1,11包含2个1,总共5个1
解析:
简单数位dp
sum记录1的数目,返回sum
ac:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;
ll a[20];
ll dp[20][20];
//状态:dp[pos][sum]:位,1的数目
ll dfs(ll pos,ll sum,bool limit)
{
if(pos == -1) return sum;
if(!limit && dp[pos][sum] != -1) return dp[pos][sum];
ll up = limit ? a[pos] : 9;
ll tmp = 0;
for(ll i = 0;i <= up;i++)
{
tmp += dfs(pos - 1,sum+(i==1),limit && i == a[pos]);
}
if(!limit) dp[pos][sum] = tmp;
return tmp;
}
ll solve(ll x)
{
ll pos = 0;
ll c=x;
while(x)
{
a[pos++] = x%10;
x /= 10;
}
return dfs(pos-1,0,1);
}
int main()
{
ll ri;
memset(dp,-1,sizeof dp);
cin>>ri;
cout<<solve(ri)<<endl;
return 0;
}
https://ac.nowcoder.com/acm/contest/163/J
题意:
求1~n中有多少个数%(该数的各数位和)==0
解析:
数位dp+暴力
这里我开始4维,开3维也能计算,但是复杂度大10倍
数位dp的核心就是记忆化搜索,极大的节省复杂度
保存smod容易(累加就可以),条件是sum%smod==0,sum就无法开呢么大的数组,sum肯定要%mod
数的长度为12,mod最多有12*9种情况,这样sum就可以%mod了
当smod==mod&&sum%mod时就返回1
ac:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[20];
ll dp[14][120][120][120];//多加一维mod,时间复杂度降低10倍
ll dfs(int len,int mod,int smod,int sum,bool limit) //目前枚举的位数,mod,各位和,取余mod的余数,限制
{
if(len==-1)
return smod==mod && sum==0;//各位和相同,且刚好能除尽
if(smod+(len+1)*9<mod)//复杂度降低40%
return 0;
if(!limit && dp[len][mod][smod][sum]!=-1)
return dp[len][mod][smod][sum];
int up=limit?a[len]:9;
ll cur=0;
for(int i=0;i<=up;i++)
{
if(i+smod>mod)//如果和已经大于的枚举mod,则不可能,复杂度降低20%
break;
cur+=dfs(len-1,mod,smod+i,(sum*10+i)%mod,limit && i==a[len]);//当这位有限制且已枚举到上限则下一位有限制
}
return limit?cur:dp[len][mod][smod][sum]=cur;
}
ll solve(ll x)
{
memset(a,0,sizeof(a));
int k=0;
while(x){
a[k++]=x%10;
x/=10;
}
ll ans=0;
for(int i=1;i<=9*k;i++) //暴力枚举每一种mod的可能,mod最多为位数*9,mod=(1~k*9),情况也不多
{ //如果不保存数位,呢么每次mod变化都要memset一次dp数组,且每次可用的记忆化搜索都很少
ans+=dfs(k-1,i,0,0,true);
}
return ans;
}
int main()
{
memset(dp,-1,sizeof(dp));
int t;
scanf("%d",&t);
for(int cas=1;cas<=t;cas++)
{
ll n;
scanf("%lld",&n);
printf("Case %d: ",cas);
printf("%lld\n",solve(n));
}
return 0;
}
http://acm.hdu.edu.cn/showproblem.php?pid=3709
解析:
一个数如果是平衡数,呢么呢个数的平衡点一定唯一
我们暴力枚举平衡点,
别忘了减去全为0的情况,00,0000,...等会被认为是多个数
ac:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll dp[20][20][2000];
int a[20];
ll dfs(ll pos,ll sign,ll sum,ll limit)//sign是平衡点位置
{
if(pos==-1)
return sum==0;
if(sum<0)
return 0;
if(!limit&&dp[pos][sign][sum]!=-1)
return dp[pos][sign][sum];
ll ans=0;
int en=limit?a[pos]:9;
for(int i=0;i<=en;i++)
{
ans+=dfs(pos-1,sign,sum+(pos-sign)*i,limit&&i==en);
}
if(!limit)
dp[pos][sign][sum]=ans;
return ans;
}
ll solve(ll x)
{
int cnt=0;
while(x)
{
a[cnt++]=x%10;
x/=10;
}
ll ans=0;
for(int i=0;i<cnt;i++)//暴力枚举平衡点
ans+=dfs(cnt-1,i,0,1);
return ans-(cnt-1);//减去 00、000 、0000...的情况,它们其实都是0
}
int main()
{
int t;
ll a,b;
memset(dp,-1,sizeof(dp));
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld",&a,&b);
printf("%lld\n",solve(b)-solve(a-1));
}
return 0;
}
https://ac.nowcoder.com/acm/contest/1168/I
题意:
1.这个整数在10进制下某一位是6.
2.这个整数在10进制下的数位和是6的倍数.
3.这个数是6的整数倍。
那么问题来了:邓志聪想知道在一定区间内与6无关的数的和。
解析:
现在枚举的某一位pos,我统计了这一位枚举i的满足条件的个数cnt,其实只要算i对总和的贡献就可以了,对于一个数而言第pos位是i,那么对求和贡献就是i*10^pos,就是十进制的权值,然后有cnt个数都满足第pos位是i,最后sum=cnt*i*10^pos.
ac:
#include<bits/stdc++.h>
#define mod 1000000007
#define ll long long
using namespace std;
ll a[20];
struct node
{
ll cnt,sum;
}dp[20][10][10];//位,和,数位和
ll p[20];
void init()
{
p[0]=1;
for(int i=1;i<=18;i++)
p[i]=(p[i-1]*10)%mod;
for(int i=0;i<20;i++){
for(int j=0;j<10;j++){
for(int k=0;k<10;k++){
dp[i][j][k].cnt=-1;
}
}
}
}
node dfs(ll pos,ll sum,ll add,bool limit)//位,和,数位和
{
if(pos == -1){
return node{sum!=0&&add!=0,0};
}
if(!limit && dp[pos][sum][add].cnt!= -1) return dp[pos][sum][add];
ll up = limit ? a[pos] : 9;
node ans,tmp;
ans.cnt=0,ans.sum=0;
for(ll i = 0;i <= up;i++)
{
if(i==6)
continue;
tmp=dfs(pos-1,(sum*10+i)%6,(add+i)%6,limit && i == a[pos]);
ans.cnt=(ans.cnt+tmp.cnt)%mod;
ans.sum=ans.sum+(tmp.sum+((p[pos]*i)%mod)*(tmp.cnt%mod))%mod;
ans.sum=ans.sum%mod;
}
if(!limit) dp[pos][sum][add]=ans;
return ans;
}
ll solve(ll x)
{
ll pos = 0;
while(x)
{
a[pos++] = x%10;
x /= 10;
}
return dfs(pos-1,0,0,true).sum;
}
int main()
{
init();
ll l,r;
while(scanf("%lld%lld",&l,&r)!=EOF)
{
ll ans=(solve(r)-solve(l-1)+mod)%mod;
printf("%lld\n",ans);
}
return 0;
}