这里是一篇讲数位dp的文章
数位dp最重要的就是状态的建立!
A - Beautiful numbers
一道数位dp的好题。
不过理清思路后也挺好理解的。
题目要求求能被它所有位数整除的数的个数,换句话说就是能被它所有位数的lcm整数的数的个数,所以我们得维护一个lcm。又因为a%p=a%(k*p)%p。所以我们可以先将所有的数模上lcm(1,2,3…9),然后在最后再判断模lcm是否为0.
那么这样开的数组大小为20*2520*2520.
很遗憾的是会mle,实际上他们的lcm不会有2520个,所以我们可以将他们的lcm离散化一下,这样数组就能开下去了。
#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
const int mod=2520;
typedef long long ll;
ll dp[maxn][mod+10][50];
int _a[maxn];
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
int lcm(int a,int b){return a/gcd(a,b)*b;}
int h[mod+1000];
ll dfs(int d,int val,int _lcm,bool limit,bool lead)
{
if(d==-1)
return val%_lcm == 0;
if(!limit&&!lead&&dp[d][val][h[_lcm]]!=-1)return dp[d][val][h[_lcm]];
int End=limit?_a[d]:9;
ll res=0;
for(int i=0;i<=End;i++)
{
if(i==0)
{
res+=dfs(d-1,(val*10)%mod,_lcm,limit&&(i==_a[d]),lead&&(!i));
}
else
{
res+=dfs(d-1,(val*10+i)%mod,lcm(_lcm,i),limit&&(i==_a[d]),lead&&(!i));
}
}
if(!limit&&!lead)
dp[d][val][h[_lcm]]=res;
return res;
}
void init()
{
int cnt=0;
for(int i=1;i<=mod;i++)
if(mod%i==0)
{
h[i]=cnt;
cnt++;
}
}
ll solve(ll num)
{
int cnt=0;
while(num)
{
_a[cnt]=num%10;
num/=10;
cnt++;
}
return dfs(cnt-1,0,1,true,true);
}
int main()
{
init();
memset(dp,-1,sizeof(dp));
int T;
scanf("%d",&T);
while(T--)
{
ll l,r;
scanf("%lld %lld",&l,&r);
printf("%lld\n",solve(r)-solve(l-1));
}
return 0;
}
B - XHXJ’s LIS
题目挺好的,会做这题必须得会nlogn求最长递增序列,因为k<=10,所以我们可以用10位的2进制来记录已经有的递增序列,该位为1就代表有这个数字。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
typedef long long ll;
ll L,R;
int K;
ll dp[50][2000][15];
ll a[50];
int getidx(int val,int num)
{
for(int i=0;i<=9;i++)
{
int tmp=(val>>i)&1;
if(tmp&&i>=num)
{
return i;
}
}
return -1;
}
int getnum(int val)
{
int res=0;
for(int i=0;i<=9;i++)
if((val>>i)&1)res++;
return res;
}
ll dfs(int d,int val,bool limit,bool lead)
{
if(d==-1)
return getnum(val)==K;
if(!limit&&!lead&&dp[d][val][K]!=-1)
return dp[d][val][K];
int End=limit?a[d]:9;
ll res=0;
for(int i=0;i<=End;i++)
{
int idx=getidx(val,i);
int Del=idx==-1?0:(1<<idx);
if(i==0)
{
if(lead)
res+=dfs(d-1,val,limit&&i==End,1);
else
res+=dfs(d-1,val-Del+(1<<i),limit&&i==End,0);
}
else
res+=dfs(d-1,val-Del+(1<<i),limit&&i==End,0);
}
if(!limit&&!lead)
dp[d][val][K]=res;
return res;
}
ll solve(ll num)
{
int cnt=0;
while(num)
{
a[cnt++]=num%10;
num/=10;
}
return dfs(cnt-1,0,true,true);
}
int main()
{
int T;
scanf("%d",&T);
int cas=1;
memset(dp,-1,sizeof(dp));
while(T--)
{
scanf("%lld %lld %d",&L,&R,&K);
printf("Case #%d: %lld\n",cas++,solve(R)-solve(L-1));
}
}
E - Round Numbers
这题一开始想复杂了,实际上把数字转换成2进制就会好做很多。另外注意要剔除前导零的影响。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
int dp[64][64][64];
int a[64];
int dfs(int d,int x,int y,bool limit,bool lead)
{
if(d==-1)return x>=y;
if(!limit&&!lead&&dp[d][x][y]!=-1)return dp[d][x][y];
int End=limit?a[d]:1;
int res=0;
for(int i=0;i<=End;i++)
{
if(i)res+=dfs(d-1,x,y+1,limit&&i==End,lead&&!i);
else
{
if(lead)
res+=dfs(d-1,x,y,limit&&i==End,1);
else
res+=dfs(d-1,x+1,y,limit&&i==End,0);
}
}
if(!limit&&!lead)dp[d][x][y]=res;
return res;
}
int solve(int num)
{
int cnt=0;
while(num)
{
a[cnt++]=num%2;
num/=2;
}
return dfs(cnt-1,0,0,true,true);
}
int main()
{
memset(dp,-1,sizeof(dp));
int L,R;
scanf("%d %d",&L,&R);
//cout<<solve(R)<<endl;
printf("%d\n",solve(R)-solve(L-1));
return 0;
}
F - Balanced Number
这题思路还是对的,结果调了好久没对,突然发现没有初始化。只要另外维护对称轴的位置及值就行了。
有一个小细节需要注意每次枚举轴都会将所有都是0的计算一次,所以最后的答案需要减掉(枚举次数-1).
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
typedef long long ll;
ll dp[20][20][2000];
int a[20];
ll dfs(int d,int loc,int sum,bool limit,bool lead)
{
if(d==-1)return sum==0;
if(sum<0)return 0;
if(!limit&&!lead&&dp[d][loc][sum]!=-1)return dp[d][loc][sum];
int End=limit?a[d]:9;
ll res=0;
for(int i=0;i<=End;i++)
{
int tmp=(d-loc)*i;
res+=dfs(d-1,loc,sum+tmp,limit&&i==End,lead&&!i);
}
if(!limit&&!lead)
dp[d][loc][sum]=res;
return res;
}
ll solve(ll num)
{
//int offset=1000;
int cnt=0;
while(num)
{
a[cnt++]=num%10;
num/=10;
}
ll res=0;
for(int i =0;i<cnt;i++)
res+=dfs(cnt-1,i,0,true,true);
return res-(cnt-1);
}
int main()
{
int T;
scanf("%d",&T);
memset(dp,-1,sizeof(dp));
while(T--)
{
ll x,y;
scanf("%lld %lld",&x,&y);
ll R=solve(y);
ll L=(x==0?0:solve(x-1));
//cout<<R<<endl;
//cout<<L<<endl;
printf("%lld\n",R-L);
}
return 0;
}
H - F(x)
这题虽然简单,但我还是t了。。
原因是因为我每次计算的时候都是从0开始,这意味着dp[pos][val] 维护的值就是从0到B中值不大于A的数。
这样每次都得将dp数组初始化。所以会T,实际上我们换种想法类比一下:上面的操作就是一直以0为起始位置,来计算能到目的地的方法个数,而目的地有很多,所以我每次都得重新计算,那我为什么不计算目的地到0的个数呢,0又不会变。
这样就不用重新计算了。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
int dp[15][5000];
int a[15];
int tab[15];
int mx;
void init()
{
tab[0]=1;
for(int i=1;i<=10;i++)tab[i]=tab[i-1]*2;
}
int dfs(int d,int val,bool limit,bool lead)
{
if(d==-1)return val>=0;
if(val<0)return 0;
if(!limit&&!lead&&dp[d][val]!=-1)return dp[d][val];
int End=limit?a[d]:9;
int res=0;
for(int i=0;i<=End;i++)
{
res+=dfs(d-1,val-tab[d]*i,limit&&i==End,lead&&!i);
}
if(!limit&&!lead)
dp[d][val]=res;
return res;
}
int solve(int num)
{
int cnt=0;
while(num)
{
a[cnt++]=num%10;
num/=10;
}
return dfs(cnt-1,mx,true,true);
}
void getmx(int A)
{
int cnt=0;
while(A)
{
a[cnt++]=A%10;
A/=10;
}
mx=0;
for(int i=cnt-1;i>=0;i--)
mx+=tab[i]*a[i];
}
int main()
{
init();
int T;
memset(dp,-1,sizeof(dp));
scanf("%d",&T);
int A,B;
int cas=1;
while(T--)
{
scanf("%d %d",&A,&B);
getmx(A);
printf("Case #%d: %d\n",cas++,solve(B));
}
return 0;
}
J - 吉哥系列故事――恨7不成妻
这题并不会做,看了题解才会的。
首先我们得维护三个值,个数,和,平方和。设当前状态为res,它的下一状态为tmp。用结构体来维护。
求个数不多说了。res.cnt+=tmp.cnt.
求和 res.sum+=tmp.sum+tmp.cnt*i(i为当前枚举的位的值)。
求平方和,设下一状态的数值为x(注意是数值)。那么
sqrsum=(10d∗i+x)2=102∗d∗i2+x2+2∗10d∗i∗x
s
q
r
s
u
m
=
(
10
d
∗
i
+
x
)
2
=
10
2
∗
d
∗
i
2
+
x
2
+
2
∗
10
d
∗
i
∗
x
对于上面的式子,我们还得乘以一个tmp.cnt(也不能说乘以tmp.cnt,就是对于这个式子我们要求出所有下一状态的平方和)。那么
x2
x
2
这一项就变成了tmp.sqrsum,
x∗tmp.cnt
x
∗
t
m
p
.
c
n
t
就变成了tmp.sum。所以我们就可以求出res.sqrsum啦。
注意一些细节,该模的地方要模就行了。
ans算出来的结果可能为负数,所以要模两次。
#include<iostream>
#include<cstring>
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll tab[20];
struct node
{
ll cnt,sum,sqsum;
node(ll _c,ll _sum,ll _sqsum):cnt(_c),sum(_sum),sqsum(_sqsum){}
node(){}
}dp[20][10][10];
int a[20];
node dfs(int d,int sum,int num,bool limit,bool lead)
{
if(d==-1)
return sum&&num?node(1,0,0):node(0,0,0);
if(!limit&&!lead&&dp[d][sum][num].cnt!=-1)return dp[d][sum][num];
int End=limit?a[d]:9;
node res=node(0,0,0);
node tmp;
for(int i=0;i<=End;i++)
{
if(i==7)continue;
tmp=dfs(d-1,(sum+i)%7,(num*10+i)%7,limit&&i==End,lead&&!i);
(res.cnt+=tmp.cnt)%=mod;
(res.sum+=(tmp.sum+(tab[d]*i)*tmp.cnt%mod)%mod)%=mod;
(res.sqsum+=((tmp.sqsum+(((tmp.sum*2)%mod*tab[d])%mod*i)%mod)%mod+(((tab[d]*i%mod)*(tab[d]*i%mod))%mod*tmp.cnt)%mod)%mod)%=mod;
}
if(!lead&&!limit)
dp[d][sum][num]=res;
return res;
}
ll solve(ll num)
{
int cnt=0;
while(num)
{
a[cnt++]=num%10;
num/=10;
}
return dfs(cnt-1,0,0,true,true).sqsum;
}
void init()
{
tab[0]=1;
for(int i=1;i<=20;i++)tab[i]=tab[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;
}
int main()
{
init();
int T;
scanf("%d",&T);
while(T--)
{
ll L,R;
scanf("%lld %lld",&L,&R);
ll ansr=solve(R);
ll ansl=solve(L-1);
ll ans=(ansr-ansl);
ans=(ans%mod+mod)%mod;
printf("%lld\n",ans);
}
return 0;
}
K - Balanced Numbers
一道简单题,维护数字的奇偶性就行了,还有维护那些数字是否出现过,所以我用3进制来维护。还好内存没炸。。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
typedef unsigned long long ll;
ll dp[25][60000];
int a[25];
ll tab[15];
void init()
{
tab[0]=1;
for(int i=1;i<=10;i++)tab[i]=tab[i-1]*3;
}
bool check(int val)
{
for(int i=0;i<=9;i++)
{
int tmp=(val/tab[i])%3;
if(tmp==0)continue;
if((tmp%2&&i%2)||(tmp%2==0&&i%2==0))
return 0;
}
return 1;
}
int change(int val,int i)
{
int _val=val;
val/=tab[i];
int tmp=val%3;
if(tmp==0)
_val+=tab[i];
else if(tmp==1)
_val+=tab[i];
else
_val-=tab[i];
return _val;
}
ll dfs(int d,int val,bool limit,bool lead)
{
if(d==-1)
{
if(check(val))return 1;
return 0;
}
if(!limit&&!lead&&dp[d][val]!=-1)return dp[d][val];
int End=limit?a[d]:9;
ll res=0;
for(int i=0;i<=End;i++)
{
if(i==0)
{
if(lead)
res+=dfs(d-1,val,limit&&i==End,1);
else
res+=dfs(d-1,change(val,i),limit&&i==End,0);
}
else
res+=dfs(d-1,change(val,i),limit&&i==End,0);
}
if(!limit&&!lead)dp[d][val]=res;
return res;
}
ll solve(ll num)
{
int cnt=0;
while(num)
{
a[cnt++]=num%10;
num/=10;
}
return dfs(cnt-1,0,true,true);
}
int main()
{
init();
int T;
memset(dp,-1,sizeof(dp));
scanf("%d",&T);
while(T--)
{
ll A,B;
scanf("%lld %lld",&A,&B);
printf("%lld\n",solve(B)-solve(A-1));
}
return 0;
}
数位dp感觉很简单有感觉很巧妙,不算是很入门吧,还得继续训练!