hdu4734 数位dp + 小技巧

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;
}
时间: 2024-11-05 01:14:27

hdu4734 数位dp + 小技巧的相关文章

数位dp小练

最近刷题的同时还得填填坑,说来你们也不信,我还不会数位dp. 照例推几篇博客: 数位DP讲解 数位dp 的简单入门 这两篇博客讲的都很好,不过代码推荐记搜的形式,不仅易于理解,还短. 数位dp的式子一般是这样的:dp[i][][]表示到第\(i\)位,而后面几维就因题而异了. 不过通用的思想就是利用前缀相减求出区间信息. 算了上题吧. [SCOI2009]windy数 这都说是数位dp入门题. 根据这题,受到影响的数只有相邻两个,因此dp[i][j]表示到第\(i\)位(从高往低)上一位的数\(

数位DP小小结

FZOJ Problem 2113Jason的特殊爱好 题意:x~y数字里面有多少个 1 思路:我们算法课实验题的简化版,当时我用了很麻烦的一个DP=_= 刚刚学到了很棒的姿势,记忆化DP!! dfs(int pos ,bool end1) ; end1==false 返回pos位后面(包含pos)任意组合有多少个 1 : end1==true 返回上一位是结尾,Pos以后的位受到限制组合有多少个 1 : 大概是这样,如果数字是 4987 现在计算到 8 这个数字,end1==true,说明是4

HDU4734(数位dp)

F(x) Time Limit: 1000/500 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 4423    Accepted Submission(s): 1632 Problem Description For a decimal number x with n digits (AnAn-1An-2 ... A2A1), we define its weight as F

2015 whu校赛f题big data(dp + 小技巧)

题意是给定五个数n(n <= 100),a,b,l,r 另外有函数序列f(x),其中f(x + 1) = f(x) + a或f(x)+ b,f(0) = 0,问有多少个这样的函数序列f(1)到f(n)使得函数序列的和在l和r之间 解题思路如下: 图片有一处错误,要减去的是a*(n + 1) * n而不是 (b - a)* (n + 1) * n,此外,要注意x/c时向上取整和向下取整的问题. 这道题做做停停一个月了今天终于找时间ac了,有点感人呐 代码如下: #include<cstdio&g

hdu 3709 数位dp(小思维)

http://acm.hdu.edu.cn/showproblem.php?pid=3709 Problem Description A balanced number is a non-negative integer that can be balanced if a pivot is placed at some digit. More specifically, imagine each digit as a box with weight indicated by the digit.

HDU4734——F(x)(数位DP)

dp[i][j]表示i位数权值不超过j的数的个数 注意点: dp[i][j]的值不用每次都初始化,因为它的值不受输入的影响,如果前面算过了就直接拿来用,没算过就拿来算并记录下来 <strong>#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<vector> #include<queue> #include&

【hdu4734】【F(x)】数位dp + 小小的总结一下

(https://www.pixiv.net/member_illust.php?mode=medium&illust_id=65608478) Problem Description For a decimal number x with n digits (AnAn-1An-2 ... A2A1), we define its weight as F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1. Now you are given

SCUT - 289 - 小O的数字 - 数位dp

https://scut.online/p/289 一个水到飞起的模板数位dp. #include<bits/stdc++.h> using namespace std; typedef long long ll; bool notp[2000]; const int MAXS1=200; const int MAXS2=2000; int a[20]; ll dp[20][MAXS1][MAXS2]; ll dfs(int pos,int s1,int s2,bool lead,bool l

[HDU4734] 不要62(数位dp入门)

>传送门< 题意:统计区间 [a,b] 中不含 4 和 62 的数字有多少个. 思路:数位dp 就是数位上不能有4也不能有连续的62,没有4的话在枚举的时候判断一下,不枚举4就可以保证状态合法了,所以这个约束没有记忆化的必要,而对于62的话,涉及到两位,当前一位是6或者不是6这两种不同情况我计数是不相同的,所以要用状态来记录不同的方案数.dp[pos][sta]表示当前第pos位,前一位是否是6的状态,这里sta只需要去0和1两种状态就可以了,不是6的情况可视为同种,不会影响计数. Code