F(x)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4734
参考博客:http://www.cnblogs.com/zinthos/p/3899606.html
数位dp
这题用艾神教的数位dp方法好像过不了,于是我去网上找了下数位dp的做法(orz艾神自创算法)
1 对于一个数,若其首位已经比询问上界小,则剩余位没有任何限制。 2 此时如果能直接处理这一情况,则问题距离解决又会迈出一大步。 3 例如,在十进制下,计算[10000,54321]内的数字和, 4 我们可以将其分解为: 5 [10000,19999],[20000,29999],[30000,39999], 6 [40000,49999],[50000,54321]。 7 前四个区间如果可以直接得到,则只需处理最后一个区间, 8 进一步将最后一个区间划分为: 9 [50000,50999],[51000,51999],[52000,52999], 10 [53000,53999],[54000,54321]。 11 同理将最后一个区间划分下去,最后可以得到以下区间划分: 12 [10000,19999],[20000,29999],[30000,39999], 13 [40000,49999],[50000,50999],[51000,51999], 14 [52000,52999],[53000,53999],[54000,54099], 15 [54100,54199],[54200,54299],[54300,54309], 16 [54310,54319],[54320,54321] 17 于是,我们只需要预处理出[10000,54321]的区间的值, 18 再暴力[54320,54321]区间,那么即可得到结果。 19 20 这个过程只需要用到记忆化搜索。 21 22 数位DP的解题思路
于是,数位dp就有了固定的解题模板:
1 typedef long long LL; 2 const int maxn=22; 3 int dig[maxn]; 4 LL f[maxn]/* [TODO] */; 5 6 LL dfs(int pos,/* TODO */,int limit){ 7 if (pos<0) return /* TODO */; 8 if (!limit&&f[pos]/* [TODO] */!=-1) return f[pos]/* [TODO] */; 9 LL res=0; 10 int last=limit?dig[pos]:9; 11 for (int i=0;i<=last;i++){ 12 res+=dfs(pos-1,/* TODO */,limit&&(i==last)); 13 } 14 if (!limit) f[pos]/* [TODO] */=res; 15 return res; 16 } 17 18 LL solve(LL n){ 19 int len=0; 20 while (n){ 21 dig[len++]=n%10; 22 n/=10; 23 } 24 return dfs(len-1,/* TODO */,1); 25 }
对于这道题来说,当前位上的数字对下一个状态来说是无关紧要的,重要的是填了这个数后剩下还能填多少值,
于是可以定义状态:dp[idx][res]表示当前为第idx位剩余数值为res的情况数。
当idx<0&&res>=0时,该状态合法;
当res<0时,状态非法。
代码如下:
1 /*苟利国家生死已,岂因祸福避趋之*/ 2 #include<cstdio> 3 #include<cstring> 4 #define I 10 //位数 5 #define RES 4600 //rest 6 using namespace std; 7 int T,A,B,dp[I][RES],ans,dig[I],bit[I]; 8 int dfs(int idx,int res,bool flag){ 9 if(idx<0)return res>=0; 10 if(res<0)return 0; 11 if(!flag&&dp[idx][res]!=-1)return dp[idx][res]; 12 int temp=0,last=flag?dig[idx]:9; 13 for(int i=0;i<=last;++i) 14 temp+=dfs(idx-1,res-i*bit[idx],flag&&i==last); 15 if(!flag)dp[idx][res]=temp; 16 return temp; 17 } 18 int solve(){ 19 int n=-1,res=0; 20 while(B){ 21 dig[++n]=B%10; 22 B/=10; 23 } 24 for(int i=0;A;A/=10,++i) 25 res+=bit[i]*(A%10); 26 return dfs(n,res,1); 27 } 28 void init(){ 29 memset(dp,-1,sizeof(dp)); 30 for(int i=0;i<10;++i) 31 bit[i]=1<<i; 32 } 33 int main(void){ 34 init(); 35 scanf("%d",&T); 36 for(int times=1;times<=T;++times){ 37 scanf("%d%d",&A,&B); 38 ans=solve(); 39 printf("Case #%d: %d ",times,ans); 40 } 41 }
总结:
艾神的算法是给所有参数都创建一个维度,直接递推得到答案,当询问条件改变时,无法使用之前得到的值。适合询问次数少的情况。
而记忆化搜索是预处理各个区间的值,最后暴力小区间,适用于询问次数较多的情况。