HDU 4734 F(x)

题意:一个整数 (AnAn-1An-2 … A2A1), 定义 F(x)=An * 2n-1+An-1 * 2n-2+… +A2 * 2+A1 * 1,求[0..B]内有多少数使得F(x)<= F(A)


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


思路:数位dp 都是泪~~~

比赛的时候,dp状态是dp[pos][pre],pre表示当前枚举到pre为多少,判断pre<=fa。但这样设计状态的一个问题是对于不同的A,dp[][]表示的状态不同,所以每个T都有memset dp数组,并且重新计算,然后就超时了哭……

后来的后来,

dp状态设计成dp[pos][remain],remain表示还剩多少fA可以分配,判断remain>=0。这样对于不同的A便不影响他的状态表示。所以不用每次T都重新计算dp[][],大大缩短了时间。

总结一下,在设计数位DP状态时最好把每次询问的变量作为初始值而不是状态转移、判定时依赖的量(比如第一个状态判定条件是pre<=fa,就依赖于当前fa,这样就不好),这样才可以最大限度的发挥记忆化的作用。

ac的代码:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define maxn 30
#define LL int

LL digit[maxn];
LL dp[maxn][20000];
LL ans;

LL dfs(LL pos, LL remain, bool doing){
    if(pos == -1) return remain >= 0;
    if(remain < 0) return 0;
    if(!doing && dp[pos][remain]!=-1) return dp[pos][remain];

    LL ans = 0,nremain;
    LL end = doing ? digit[pos] : 9;
    for(LL i = 0;i <= end;i++){
        nremain = remain - i *(1 << pos);
        ans += dfs(pos - 1, nremain , doing && i==end);
    }
    if(!doing) dp[pos][remain] = ans;
    return ans;
}
LL calc(LL x){
    LL pos = 0;
    while(x){
        digit[pos++] = x %10;
        x /= 10;
    }
    return dfs(pos - 1, ans , 1);
}


LL f(LL x){
    LL sum = 0;
    LL carr = 1;
    while(x){
        sum += (x%10) * carr;
        carr *= 2;
        x /= 10;
    }
    return sum;
}

LL a,b;
int main()
{
    int T;
    scanf("%d",&T);
    memset(dp,-1,sizeof(dp));
    for(int ncase = 1;ncase <= T; ncase ++){
        scanf("%d %d",&a,&b);
        ans = f(a);
        printf("Case #%d: %d\n",ncase,calc(b));
    }
    return 0;
}



TLE的代码:


#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define maxn 30
#define LL int

LL digit[maxn];
LL dp[maxn][20000];
LL ans;

LL dfs(LL pos, LL pre, bool doing){
    if(pos == -1) return pre <= ans;
    if(pre > ans) return 0;
    if(!doing && dp[pos][pre]!=-1) return dp[pos][pre];

    LL ans = 0,npre;
    LL end = doing ? digit[pos] : 9;
    for(LL i = 0;i <= end; i++){
        npre = pre + i *(1 << pos);
        ans += dfs(pos - 1, npre , doing && i==end);
    }
    if(!doing) dp[pos][pre] = ans;
    return ans;
}
LL calc(LL x){
    memset(dp,-1,sizeof(dp));
    LL pos = 0;
    while(x){
        digit[pos++] = x %10;
        x /= 10;
    }
    return dfs(pos - 1, 0 , 1);
}


LL f(LL x){
    LL sum = 0;
    LL carr = 1;
    while(x){
        sum += x%10 * carr;
        carr *= 2;
        x /= 10;
    }
    return sum;
}

LL a,b;
int main()
{
    int T;
    scanf("%d",&T);
    //memset(dp,-1,sizeof(dp));
    for(int ncase = 1;ncase <= T; ncase ++){
        scanf("%d %d",&a,&b);
        ans = f(a);
        //printf("ans =  %I64d\n",ans);
        printf("Case #%d: %d\n",ncase,calc(b));
    }
    return 0;
}


原来这份TLE的代码也是可以A的,

剪枝到位就行,可惜因为重复计算的次数太多,导致记忆化的优势没有完全显示出来,

我们可以考虑到转化中,每个二进制的个数最大为9,当我们算到最大值的时候,如果都小于fa,就可以直接进位,少了很多不必要的进位

代码:


#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define maxn 10
#define LL int

LL digit[maxn];
LL dp[maxn][5000];
LL ans;
int c[10];

LL dfs(LL pos, LL pre, bool doing){
    if(pos == -1) return pre <= ans;
    if(!doing && dp[pos][pre]!=-1) return dp[pos][pre];

    LL sum = 0,npre;
    LL end = doing ? digit[pos] : 9;
    for(LL i = 0;i <= end; i++){
        npre = pre + i *(1<<pos);
        if(!doing && npre + 9 *( (1<<pos) - 1) <= ans) sum += c[pos];
        else if(npre > ans) continue;
        else{
            sum += dfs(pos - 1, npre , doing && i == end);
        }
    }

    if(!doing) dp[pos][pre] = sum;
    return sum;
}
LL calc(LL x){
    memset(dp,-1,sizeof(dp));
    LL pos = 0;
    while(x){
        digit[pos++] = x %10;
        x /= 10;
    }
    return dfs(pos - 1, 0 , 1);
}

LL a,b;
int main()
{
    int T,carr;
    c[0]=1;
    for(int i=1;i<10;i++) c[i]=c[i-1]*10;
    scanf("%d",&T);
    for(int ncase = 1;ncase <= T; ncase ++){
        scanf("%d %d",&a,&b);
        ans = 0;
        carr = 1;
        while(a){
            ans += (a%10) * carr;
            carr *= 2;
            a /= 10;
        }
        printf("Case #%d: %d\n",ncase,calc(b));
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值