首页 > 代码库 > F(x)

F(x)

F(x)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4734

参考博客:http://www.cnblogs.com/zinthos/p/3899606.html

数位dp

这题用艾神教的数位dp方法好像过不了,于是我去网上找了下数位dp的做法(orz艾神自创算法)

技术分享
对于一个数,若其首位已经比询问上界小,则剩余位没有任何限制。
此时如果能直接处理这一情况,则问题距离解决又会迈出一大步。
例如,在十进制下,计算[10000,54321]内的数字和,
我们可以将其分解为:
[10000,19999],[20000,29999],[30000,39999],
[40000,49999],[50000,54321]。
前四个区间如果可以直接得到,则只需处理最后一个区间,
进一步将最后一个区间划分为:
[50000,50999],[51000,51999],[52000,52999],
[53000,53999],[54000,54321]。
同理将最后一个区间划分下去,最后可以得到以下区间划分:
[10000,19999],[20000,29999],[30000,39999],
[40000,49999],[50000,50999],[51000,51999],
[52000,52999],[53000,53999],[54000,54099],
[54100,54199],[54200,54299],[54300,54309],
[54310,54319],[54320,54321]
于是,我们只需要预处理除[54320,54321]的区间的值,
再暴力[54320,54321]区间,那么即可得到结果。

这个过程只需要用到记忆化搜索。
数位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模板

对于这道题来说,当前位上的数字对下一个状态来说是无关紧要的,重要的是填了这个数后剩下还能填多少值,

于是可以定义状态: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\n",times,ans);
40     }
41 }

 总结:

艾神的算法是给所有参数都创建一个维度,直接递推得到答案,当询问条件改变时,无法使用之前得到的值。适合询问次数少的情况。

而记忆化搜索是预处理各个区间的值,最后暴力小区间,适用于询问次数较多的情况。

F(x)