hdu4734 数位dp + 小技巧

时间:2022-12-16 10:46:10

hdu-4734

题意:假设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;
}