HDU-6156 Palindrome Function(数位DP)

时间:2024-09-30 15:37:38

一、题目

  HDU-6156 Palindrome Function(数位DP)

二、思路

  1、这是很明显的数位DP;

  2、和以往数位DP不同的是,这里带了个进制进来,而以往做是纯十进制下或者纯二进制下做操作。但是,不管多少进制,原理都是一样的;

  3、这里有个小坑,题目中说大于10的数用A、B、C、……、Z表示,那都是骗人的。分解给定数字的每一位数后,得到每一位的就是在给定进制下的该位的权值,压根不需要在数字、字母之间转来转去,纯数字娱乐;

  4、比较直观的想法是:

    (1)枚举给定范围内的每一个进制;

    (2)计算在b进制下的回文数个数,然后即可得出在b进制下的和;

    (3)累加上面的所有和,得出最终答案。

  5、在4(2)中,利用数位DP计算在b进制下的回文数个数,需要记忆化。如何记忆化,怎么表示状态,这个是本题的关键所在。首先,当前搜索状态在数位中的位置pos,进制b,这个是确定状态的两个依据,不难想到。接下来,先把dp数组的设计放下来,看看dfs函数。既然与回文数有关,那自然,回文序列的起始位置start应该知道,因为前导0是不参与回文计算的。还有一点非常关键:因为要判断一个数是否回文,就必须从端点到中间扫描整个数字序列,而如果要这么做,那我们的记忆化就玩完了,每个数都判断一下,就变成暴力枚举了。所以,要让判断在搜索过程中完成。因此,需要使用一个数组,记录搜索的数字序列。注意,判断这个数字序列是不是回文的一定不是在一个数字所有位搜索完成之后再判断的,否则,就无法记忆化了。因为我们记录了数字序列最高位的起始位置,所以,在搜索过程中,当当前位置<=数字序列中点(start + 1) / 2时,开始做“回文比较”。如果当前已比较的数位对称了,继续比较下一位。当前已比较的数位对称的意思是,形如1213031……的数字序列,假设0所在的位置是这个数字序列的中心点。当当前搜索完省略号“……”前的一个1时,省略号前的31已经和0前面的13形成回文了,就是我所说的数位对称了。当下次使用数字序列3456065……的数字序列再次到达这个状态(注意,我们目前已确定的三个状态标志是,当前搜索状态在数位中的位置pos、进制b、回文序列的起始位置start)时,之后的操作完全和使用之前的序列1213031……到达这个状态后的操作一样。为什么呢?对于第一个数字序列1213031……而言,不管31之后的两位数(因为我们假设了数字序列的中心点是0的所在位置,所以,后面省略号部分其实只有两位,对应前面的12)是什么,肯定能找到21和前面的12对称。同样的道理,对于第二个数字序列3456065……而言,搜索省略号前的5这个位置后,不管后面省略号部分是什么,我总能找到一个43和前面的34对称。所以,在前面的1213031……的序列中,搜索完省略号部分后,当序列3456065……到达这个状态时,直接使用前面记忆化的结果即可,而无需再次向下搜索。这时有个比较疑惑的地方:如果后面省略号部分无法到达43呢,比如说,搜索上界就是345606540。注意,如果有上界限制,我们的程序是不会使用记忆化的结果的。所以,从前面的讨论来看,确定一个状态,还需要一个东西,当前已搜索过的序列是否构成回文串。比如前面的例子,1213031……,从中心点0位置开始,0 == 0,搜索下一位,0后面的3 == 0前面的3,0后面的1 == 0前面的3前面的1……,搜索完这个状态后,当前已搜索过的序列是已经构成回文串的。同样地,如果序列是1213032……,当比较省略号前的2和0前面的3前面的1时,发现不相等,那后面如何,这个序列都不会是回文串了。但是,这也是一种状态,记忆化时需要区分开。所以,说了这么多,可以确定记忆化数组dp的四个状态标志了:①当前搜索状态在数位中的位置pos、②进制b、③回文序列的起始位置start、④搜索到当前位置时,之前搜索过的序列是否已经部分回文。这里的第④个标志特别要注意,已部分回文,不是这样的,11011……(认为这里的11011是回文的)。而是,从这个数字序列的中心位置开始,是否每一位都和其对应位相等。如果是,那么,那就满足④,是已部分回文的。注意,一定要到达中心位置后才能开始判断。

三、注意点

  1、这题不需要把前导0作为限制条件加到dfs函数中,有了pos和start就行了。否则,很多本可以记忆化的,加了!lead限制条件后,不能使用记忆化结果,也不能记忆化搜索值。效率大大降低。导致代码在HDU上超时。

  2、我用这个测试样例(http://public-1252154746.cosgz.myqcloud.com/acmdata/hdu6156input.txt),加了前导0的限制条件,耗时11s左右。不加该条件,耗时5s左右。在HDUOJ上运行耗时2100ms左右。对应的输出文件:http://public-1252154746.cosgz.myqcloud.com/acmdata/hdu6156output.txt

四、源代码

  

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
LL dp[][][][];//注意,因为最小进制为2,所以,pos最大可能是30(因为x和y最大是1e9),要确保大小足够。
], dn, bit[];

template <class T> inline void read(T &x) {
    int t;
    bool flag = false;
    ')) ;
    if(t == '-') flag = true, t = getchar();
    x = t - ';
     + t - ';
    if(flag) x = -x;
}

LL dfs(int pos, int base, int start, bool ispd, bool limit) {
    )return ispd;
    )return dp[pos][base][start][ispd];
    ;
    LL ans = ;
    ; i <= top; ++i) {
        bit[pos] = i;
        )ans += dfs(pos - , , ispd, limit && i == top);
        else {
            ) >> ;
            , base, start, ispd, limit && i == top);
            , ], limit && i == top);
            /**else这里的第4个参数不需要ispd && i == bit[start - pos + 1],因为只要某位和对应位不相等,
            那么之后的搜索都会从前面的if中进去。*/
        }
    }
    if(!limit)dp[pos][base][start][ispd] = ans;
    return ans;
}

LL solve(LL n, int b) {
    dn = ;
    ) {
        digit[++dn] = n % b;
        n /= b;
    }
    return dfs(dn, b, dn, true, true);
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif
    LL L, R, l, r, T, ans;
    memset(dp, -, sizeof(dp));
    scanf("%lld", &T);
    ; t <= T; ++t) {
        ans = ;
        read(L), read(R), read(l), read(r);
        for(int b = l; b <= r; ++b) {
            LL ans1 = solve(R, b), ans2 = solve(L - , b);
            ans += (ans1 - ans2) * b + (R - L +  - (ans1 - ans2));
        }
        printf("Case #%lld: %lld\n", t, ans);
    }
    ;
}