HDU 4734 F(x)

传送门

数位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)

然而,本题有一个很大的坑!!!!!!

那就是,若想把dp的初始化(memset)提到多组数据之外的话,则必须用减法来计算and判断状态。

本题可以有两种做法。(然而第一种会超时的)

  1. 使用加法,初始状态是0,判断是否>F(A)。dp的第二维存储最高位到第i+1位的部分F(x)值。
    memset(dp,-1,sizeof dp)必须对每组数据都使用。
  2. 使用减法,初始状态是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;
}

你可能感兴趣的:(动态规划,HDOJ,动态规划,-,数位dp)