传送门
数位dp。(大坑预警)
给一个函数F(x)
,然后问你对于[0,B]
中的每个数x
,满足F(x)<=F(A)
的x
的个数。
inclusive表示闭区间。
可以看出,题目定义的这个函数和每个数位相关。
所以,在对每个数位进行dfs的过程中,随着这个数一点点被确定,这个数的F(x)
也逐渐被确定(而且这个过程是单调递增的,非严格)。
所以可以想到dp的第二维表示:从最高位到第i+1
位的部分F(x)
值。
所以这个状态的容量还挺大的,写了个程序算了下,F(1e9-1)=4599
为什么这题不用考虑前导0?
因为对于任何一个数x
而言,无论它有没有前导0、有几个前导0,都不会改变其F(x)
本题可以有两种做法。(然而第一种会超时的)
0
,判断是否>F(A)
。dp的第二维存储最高位到第i+1
位的部分F(x)
值。memset(dp,-1,sizeof dp)
必须对每组数据都使用。F(A)
,判断是否<0
。dp的第二维存储第i
位到最低位最多还能够增加的F(x)
值。memset(dp,-1,sizeof dp)
可以提到多组数据之外。对第一种方法而言,"判断是否>F(A)
"并没有体现在dp的状态中,所以这样产生的dp值是依赖于A
的,仅能应用在当前数据上。
而第二种方法消除了状态与输入的相关性。(无论你A
取什么值,dp[i][j]
都是一个常量,永远不会改变。)
如此简单的转变,带来了千百倍的时间效率提升,真乃神奇。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int T, A, B;
int dp[9][4600]; // f(A)最大4599。从第i位枚举到最低位,满足f(x)<=j的x的个数
int num[9];
int f(int x)
{
int cnt = 0;
int pos = 0;
for (; x;)
{
cnt += (x % 10) * (1 << (pos++));
x /= 10;
}
return cnt;
}
int dfs(int pos, int status, bool limit) // 这道题前导0也不影响
{
//printf("dfs(%d, %d, %s)\n", pos, status, limit ? "true" : "false");
if (pos == -1) return 1;
if ((!limit) && (dp[pos][status] != -1)) return dp[pos][status];
int cnt = 0;
int up = (limit ? num[pos] : 9);
for (int i = 0; i <= up; i++)
{
int new_status = status - i*(1 << pos);
if (new_status < 0) continue;
cnt += dfs(pos - 1, new_status, limit && (i == up));
}
if (!limit) dp[pos][status] = cnt;
return cnt;
}
int solve(int x)
{
int pos = 0;
for (; x;)
{
num[pos++] = x % 10;
x /= 10;
}
return dfs(pos - 1, f(A), true);
}
int main()
{
scanf("%d", &T);
memset(dp, -1, sizeof dp); //
for (int cnt = 1; cnt <= T; cnt++)
{
scanf("%d%d", &A, &B);
printf("Case #%d: %d\n", cnt, solve(B)); // 题目问的就是[0,B] inclusive
}
return 0;
}