hdu4734 数位dp + 小技巧
题意:假设x的10进制数每一位分别为(AnAn-1An-2 ... A2A1),定义 F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1,求1-b中F(x)<=F(a)的数有多少个
思路:其实F(x)只是给每一个数位带上一个权值v=2^(p-1),F(x)最大是值不会超过5000,我们完全可以抛开权值来思考,写代码的时候再加上权值即可,这样思考和写草稿之类的会方便很多,不考虑每一位的权值的话即是数位的前缀和,
很容易想到一个dp式是dp[pos][sum];表示当前在第pos位,前缀和为sum的答案,快速把数位dp拍完,交上去,然后会发现T掉了,这题的时限只有500ms,T的原因是什么呢,就是记忆化不够彻底,在做数位dp入门题 不要62 的时候可能有点人会注意,dp数组只需要初始化一次即可,这是因为不要62题意中是不包含4和连续的62的数的个数,这里的条件是一个数本身的性质,也就是说,一个数有没有4或者连续的62和你输入的l r区间是无关的,比如1234是不合法的,无论你输入1 - 1000 还是 1 - 10000,1234都是不合法的,但是这里是不一样的,题意要求小于F(a),而a是输入的,而dp数组定义是:dp[pos][sum],表示当前在第pos位,前缀和为sum的答案, 如假设上限是55555, 那么123xxx 和 321xxx 的答案都是 dp[3][6] ,这就是记忆化的结果,因为我不需要考虑前面具体的数的123 还是321 114之类的,只要他们的前缀和sum相同并且在同样的数位,后面xxx的情况都是一样的,因为后面的约束条件都是和不大于F(a)-sum,问题就是在这个地方,由于我的记忆话是和输入的a也就是F(a)有关,所以我不能把它当作一个数的性质来记忆话,比如同样是123xxx,对于F(a)=10和F(a)=20,后面xxx可行的情况是不一样的,a=10的时候,后面的约束是不大于F(a)-sum=4,a=20的时候约束为不大于F(a)-sum=14,从而导致了每次输入不同的a都要重新初始化dp数组,从新搜索一次。这里可以通过队dp式的定义做一点小小的改变,使得约束条件变为与F(a)无关,从而不需要每次初始化重新搜索,定义dp[pos][sum],表示当前在第pos位,F(a)-sum的答案(sum为到第pos位为止的数位前缀和),这时在第pos位的约束条件是后面所有的数的和不大于sum,这时记忆化的是sum,是与F(a)无关的,比如当F(a)=10和F(a)=20时,F(a)=10时,123xxx、321xxx等对应的是dp[3][4], F(a)=20时,556xxx、466xxx对应的也是dp[3][4],因为2种情况的pos和F(a)-sum都是相同的,所以记忆化一次即可,不必初始化
AC代码:
#include "iostream" #include "iomanip" #include "string.h" #include "stack" #include "queue" #include "string" #include "vector" #include "set" #include "map" #include "algorithm" #include "stdio.h" #include "math.h" #pragma comment(linker, "/STACK:102400000,102400000") #define bug(x) cout<<x<<" "<<"UUUUU"<<endl; #define mem(a,x) memset(a,x,sizeof(a)) #define step(x) fixed<< setprecision(x)<< #define mp(x,y) make_pair(x,y) #define pb(x) push_back(x) #define ll long long #define endl ("\n") #define ft first #define sd second #define lrt (rt<<1) #define rrt (rt<<1|1) using namespace std; const ll mod=1e9+7; const ll INF = 1e18+1LL; const int inf = 1e9+1e8; const double PI=acos(-1.0); const int N=1e4+100; int a,b,fa,bit[25]; int dp[25][N]; int dfs(int limit, int sum, int pos){ if(pos==-1) return sum<=fa; if(sum>fa) return 0; if(!limit && dp[pos][fa-sum]!=-1) return dp[pos][fa-sum]; int up=limit?bit[pos]:9, ans=0; for(int i=0; i<=up; ++i){ ans+=dfs(limit&&i==bit[pos], sum+i*(1<<(pos)), pos-1); } if(!limit) dp[pos][fa-sum]=ans; return ans; } int solve(int s, int x){ int t=0,k=1; fa=0; while(x>0){ bit[t++]=x%10; x/=10; } while(s>0){ fa+=(s%10)*k; s/=10, k*=2; } return dfs(1,0,t-1); } int main(){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); mem(dp,-1); int T,cas=0; cin>>T; while(T--){ cin>>a>>b; cout<<"Case #"<<++cas<<": "; cout<<solve(a,b)<<endl; } return 0; }
再贴T的代码:
#include "bits/stdc++.h" using namespace std; int fa,bit[25],dp[25][10000]; int dfs(int limit, int pos, int sum){ if(pos==-1) return sum<=fa; if(sum>fa) return 0; if(!limit && dp[pos][sum]!=-1) return dp[pos][sum]; int up=limit?bit[pos]:9, ans=0; for(int i=0; i<=up; ++i){ ans+=dfs(limit&&i==bit[pos], pos-1, sum+i*(1<<pos)); } if(!limit) dp[pos][sum]=ans; return ans; } int solve(int a, int b){ int p=0,k=1;fa=0; while(a>0){ fa+=a%10*k; a/=10, k<<=1; } while(b>0){ bit[p++]=b%10; b/=10; } return dfs(1,p-1,0); } int main(){ int a,b,T,cas=0; memset(dp,-1,sizeof(dp)); scanf("%d",&T); while(T--){ scanf("%d%d",&a,&b); printf("Case #%d: %d\n",++cas,solve(a,b)); } return 0; }