暑期专题五 数位DP

时间:2021-08-22
本文章向大家介绍暑期专题五 数位DP,主要包括暑期专题五 数位DP使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1.Bomb

给一个数字\(N\),求\(1\)~\(N\)有多少个数字的序列包含\(“49”\)子序列。

Input

第一行输入由整数\(T(1 <= T <= 10000)\)组成,表示测试用例的数量。对于每个测试用例,将有一个整数\(N(1 <= N <= 2 ^ {63}-1)\)作为描述。

Output

对于每个测试用例,输出一个数字代表1到N有多少个包含49的数字。

思路:一道非常简单的数位DP,采用记忆化搜索实现,只需要记录有多少位,然后当前位是多少即可。

#include <bits/stdc++.h>

using namespace std;

#define ll long long

ll n, p[30], len, num[30],  f[25][2];

ll dp(int now, int lim, int hv)
{
	if (!now) return 0;
	if (lim && ~f[now][hv]) return f[now][hv];
	int up = lim ? 9 : num[now];
	ll res = 0;
	for (int i = 0; i <= up; i++)
	{
		if (hv == 1 && i == 9) res += lim ? p[now] : (n % p[now] + 1);
		else res += dp(now - 1, lim | (i != up),i == 4);
	}
	return lim ? f[now][hv] = res : res;
}

int main() 
{
	int T;
	scanf("%d", &T);
	memset(f, -1, sizeof f);
	while (T--) 
	{
		scanf("%lld", &n);
		ll n_ = n;
		len = 0;
		while (n_) num[++len] = n_ % 10, n_ /= 10;
		p[1] = 1;
		for (int i = 2; i <= len; i++) p[i] = p[i - 1] * 10;
		printf("%lld\n", dp(len, 0, 0));
	}
	return 0;
}

2.F(x)

我们定义十进制数\(x\)的权值为\(f(x) = a(n)*2^{(n-1)}+a(n-1)*2^{(n-2)}+...a(2)*2+a(1)*1\)\(a(i)\)表示十进制数\(x\)中第\(i\)位的数字。   题目给出\(a,b\),求出\(0\)~\(b\)有多少个权值不大于\(f(a)\)的数。
Input
The first line has a number \(T (T <= 10000)\) , indicating the number of test cases.
For each test case, there are two numbers A and B \((0 <= A,B < 10^9)\)
Output
For every case,you should output "Case #t: " at first, without quotes. The t is the case number starting from \(1\). Then output the answer.

思路:同样是非常简单的一道数位DP,但是有一个点,就是如果直接记录权值不太可行,因为记忆化搜索,f的权值在不同限制下方案数显然不同,所以转变思路,f开二维,一维记数位,另一位记剩余可用的权值,这样的话记忆化搜索的结果就可以直接使用了。

#include <bits/stdc++.h>

using namespace std;

int num[15], f[15][6005], p10[15], p2[15];

int dfs(int now, bool lim, int val)
{
	if (val < 0) return 0;
	if (!now) return 1;
	if (!lim && ~f[now][val]) return f[now][val];
	int mx = lim ? num[now] : 9, ans = 0;
	for (int i = 0; i <= mx; i++) ans += dfs(now - 1, lim && i == mx, val - i * p2[now]);
	return lim ? ans : f[now][val] = ans;
}

inline void solve(int t)
{
	p10[1] = p2[1] = 1;
	for (int i = 2; i <= 10; i++) p10[i] = p10[i - 1] * 10;
	for (int i = 2; i <= 10; i++) p2[i] = p2[i - 1] * 2;
	int a, b, len = 0, ff = 0;
	scanf("%d%d", &a, &b);
	while (b) num[++len] = b % 10, b /= 10; 
	for (int i = 0; a; i++)
	{
		ff += (1 << i) * (a % 10);
		a /= 10;
	}
	printf("Case #%d: %d\n", t, dfs(len, 1, ff));
}

int main()
{
	memset(f, -1, sizeof f);
	int t, T = 0;
	scanf("%d", &t);
	while (t--) solve(++T);
}

3.吉哥系列故事——恨\(7\)不成妻

单身!
  依然单身!
  吉哥依然单身!
  DS级码农吉哥依然单身!
  所以,他生平最恨情人节,不管是\(214\)还是\(77\),他都讨厌!
  
  吉哥观察了\(214\)\(77\)这两个数,发现:
  \(2+1+4=7\)
  \(7+7=7*2\)
  \(77=7*11\)
  最终,他发现原来这一切归根到底都是因为和\(7\)有关!所以,他现在甚至讨厌一切和7有关的数!

  什么样的数和\(7\)有关呢?

  如果一个整数符合下面\(3\)个条件之一,那么我们就说这个整数和\(7\)有关——
  1、整数中某一位是\(7\)
  2、整数的每一位加起来的和是\(7\)的整数倍;
  3、这个整数是\(7\)的整数倍;

  现在问题来了:吉哥想知道在一定区间内和\(7\)无关的数字的平方和。

Input

输入数据的第一行是\(case\)\(T(1 <= T <= 50)\),然后接下来的\(T\)行表示\(T\)\(case\);每个\(case\)在一行内包含两个正整数\(L, R(1 <= L <= R <= 10^{18})\)

Output

请计算\([L,R]\)中和\(7\)无关的数字的平方和,并将结果对\(10^9 + 7\)求模后输出。

思路:相比前面的题,这个题的难度就有点大了。首先满足没有任何一位是\(7\)是非常简单的,那么如何处理后面两个限制呢,记忆化搜索的时候就记录一下每一位的和除以\(7\)的余数,以及这个整数除以\(7\)的余数,然后平方和的话就要处理一下一次方的和,然后稍微转化一下就行了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const ll mod = 1e9 + 7;

struct dig {
	ll x, y, z;
}f[105][105][105];
ll p[105], num[105];

dig dfs(ll now, ll x, ll y, bool lim)
{
	if (!now) return dig{0, 0, x && y};
	if (!lim && ~f[now][x][y].z) return f[now][x][y];
	ll mx = lim ? num[now] : 9;
	dig res = dig{0, 0, 0};
	for (ll i = 0; i <= mx; i++)
	{
		if (i == 7) continue;
		dig tmp = dfs(now - 1, (x + i) % 7, (y * 10 + i) % 7, lim && i == mx);
		res.z = (res.z + tmp.z) % mod;
		res.x = (res.x + tmp.x + i * p[now] % mod * tmp.z % mod) % mod;
		res.y = (res.y + tmp.y + 2 * i * p[now] % mod * tmp.x % mod) % mod;
		res.y = (res.y + tmp.z * i % mod * p[now] % mod * i % mod * p[now] % mod) % mod;
	} return !lim ? f[now][x][y] = res : res;
}

ll cal(ll x)
{
	ll len = 0;
	if (!x) return 0;
	while (x) num[++len] = x % 10, x /= 10;
	dig tmp = dfs(len, 0, 0, 1);
	return tmp.y;
}

inline void solve()
{
	ll a, b;
	scanf("%lld%lld", &a, &b);
	printf("%lld\n", (cal(b) - cal(a - 1) + mod) % mod);
}

signed main()
{
	memset(f, -1, sizeof f);
	p[1] = 1;
	for (ll i = 2; i <= 100; i++) p[i] = p[i - 1] * 10 % mod;
	ll t;
	scanf("%lld", &t);
	while (t--) solve();
}

4.XHXJ's LIS

给定一个区间中,将区间的每一个数看成一个字符串,求这个区间内每个字符串的最大上升子序列等于\(k\)的个数。

Input

First a integer \(T(T<=10000)\),then \(T\) lines follow, every line has three positive integer \(L,R,K.( 0<L<=R<2^{63}-1\) and \(1<=K<=10).\)

Output

For each query, print "Case #t: ans" in a line, in which t is the number of the test case starting from 1 and ans is the answer.

思路:如果最长上升子序列里面没有比当前数还大的,直接将当前数插入即得到新的最长上升子序列,否则我们找到比当前数大的所有数里最小的,用当前数替换即可。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

ll k, f[25][10005][11], num[25];

ll cnt(ll x)
{
	ll ans = 0;
	while (x) 
	{
		if (x & 1) ++ans;
		x >>= 1;
	}
	return ans;
}

ll pos(ll x, ll now)
{
	for (ll i = now; i <= 9; i++) if (x & (1 << i)) return ((x ^ (1 << i)) | (1 << now));
	return (x | (1 << now));
}

ll dfs(ll now, ll p, bool lim, bool ld)
{
	if (!now) return cnt(p) == k;
	if (!lim && ~f[now][p][k]) return f[now][p][k];
	ll mx = lim ? num[now] : 9, ans = 0;
	for (ll i = 0; i <= mx; i++) ans += dfs(now - 1, ld && !i ? 0 : pos(p, i), lim && i == mx, ld && !i);
	return !lim ? f[now][p][k] = ans : ans;
}

ll cal(ll x)
{
	ll len = 0;
    while (x) num[++len] = x % 10, x /= 10;
    return dfs(len, 0, 1, 1);
}

inline void solve(ll t)
{
	ll a, b;
	scanf("%lld%lld%lld", &a, &b, &k);
	printf("Case #%lld: %lld\n", t, cal(b) - cal(a - 1));
}

signed main()
{
	memset(f, -1, sizeof f);
	ll t, T = 0;
	scanf("%lld", &t);
	while (t--) solve(++T);
}

5.Zhu’s Math Problem

Zhu is a powerful ACMer/OverWatcher, now a salt-fish comes to ask Zhu a difficult problem. Zhu thinks that problem is so easy, so he wants to know whether you can solve it?
The problem content are as follows.
You are given four integers \(A , B , C , D\) , there are many different \((a,b,c,d)\) satisfy \(a+c>b+d\) && \(a+d≥b+c\) && \(0≤a≤A\) && \(0≤b≤B\) && \(0≤c≤C\) && \(0≤d≤D\) ?

Input

First Line is an positive integer \(T(T \leq 1000)\) , represents there are \(T\) test cases.
Four each test:
The first line contain four integers \(A , B , C , D.\)
You can assume that \(0 \leq A,B,C,D \leq 10^{18}\)
Output
For each test case, output an positive integer represents answer, because the answer may be large , so you are only need to output answer mod \(10^9+7\)

思路:略加思考的话,不难发现一个结论,就是判断\(a+b\)\(c+d\)的大小关系时,如果从高到低有一位两个数的差大于等于2,那么两个数的大小关系就已确定。因为低位进位最多只能进\(1\)。有了这个性质,我们直接暴力枚举\(a+c-b-d\)\(a+d-b-c\)就行了。但是这样的话时空都无法承受,于是考虑转化为二进制来枚举,单次枚举的时间复杂度就从\(O(10^4)\)减少到\(O(2^4)\)了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const ll mod = 1e9 + 7;

ll f[67][4][4][16], num2[67], num3[67], num4[67], num1[67];

ll dfs(ll now, ll c1, ll c2, ll lim)
{
	if (!now) return c1 > 0 && c2 >= 0;
	if (~f[now][c1 + 1][c2 + 1][lim]) return f[now][c1 + 1][c2 + 1][lim];
	ll mx1 = (lim & 8) ? num1[now] : 1, mx2 = (lim & 4) ? num2[now] : 1, mx3 = (lim & 2) ? num3[now] : 1, mx4 = (lim & 1) ? num4[now] : 1, ans = 0;
	for (ll a = 0; a <= mx1; a++) for (ll b = 0; b <= mx2; b++) for (ll c = 0; c <= mx3; c++) for (ll d = 0; d <= mx4; d++)
	{
		ll tmp1 = min(2ll, c1 * 2 + a + c - b - d), tmp2 = min(2ll, c2 * 2 + a + d - b - c), l = 0;
		if (tmp1 <= -2 || tmp2 <= -2) continue;
		if (a == mx1) l |= 8; if (b == mx2) l |= 4; if (c == mx3) l |= 2; if (d == mx4) l |= 1;
		ans = (ans + dfs(now - 1, tmp1, tmp2, lim & l)) % mod;
	} return f[now][c1 + 1][c2 + 1][lim] = ans;
}

inline void solve()
{
	memset(f, -1, sizeof f);
	ll a, b, c, d, len = 0;
	scanf("%lld%lld%lld%lld", &a, &b, &c, &d);
    while (a || b || c || d)
    {
    	num1[++len] = (a & 1), a >>= 1;
    	num2[len] = (b & 1), b >>= 1;
    	num3[len] = (c & 1), c >>= 1;
    	num4[len] = (d & 1), d >>= 1;
	} printf("%lld\n", dfs(len, 0, 0, 15));
}

signed main()
{
	ll t;
	scanf("%lld", &t);
	while (t--) solve();
}

6.Balanced Numbers

Balanced numbers have been used by mathematicians for centuries. A positive integer is considered a balanced number if:

  1.  Every **even** digit appears an **odd** number of times in its decimal representation
    
  2.  Every **odd** digit appears an **even** number of times in its decimal representation
    

For example, \(77\), \(211\), \(6222\) and \(112334445555677\) are balanced numbers while \(351\), \(21\), and \(662\) are not.

Given an interval \([A, B]\), your task is to find the amount of balanced numbers in \([A, B]\) where both \(A\) and \(B\) are included.

Input

The first line contains an integer \(T\) representing the number of test cases.

A test case consists of two numbers A and B separated by a single space representing the interval. You may assume that 1 <= A <= B <= 1019

Output

For each test case, you need to write a number in a single line: the amount of balanced numbers in the corresponding interval

思路:解法较为新颖,对于每一个数出现的次数,我们用三进制的状态来表示。其中\(0\)代表没有出现过,\(1\)代表出现了奇数次,\(2\)代表出现了偶数次,那么这个状态就很好转移了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

ll num[25], tmp[25], p[25], f[25][100005];

ll get(ll val, ll x)
{
	for (ll i = 0; i <= 9; i++) tmp[i] = val % 3, val /= 3;
	if (tmp[x] == 1) tmp[x] = 2;
	else tmp[x] = 1; 
	ll ans = 0;
	for (ll i = 9; i >= 0; i--) ans = ans * 3 + tmp[i];
	return ans;
}

bool judge(ll val)
{
	for (ll i = 0; i <= 9; i++) tmp[i] = val % 3, val /= 3;
	for (ll i = 0; i <= 9; i++) 
	{
		if ((tmp[i] == 1) && (i % 2)) return 0;
		if ((tmp[i] == 2) && (i % 2 == 0)) return 0;
	} return 1;
}

ll dfs(ll now, ll val, bool lim, bool ld)
{
	if (!now) return judge(val);
	if (!lim && ~f[now][val]) return f[now][val];
	ll mx = lim ? num[now] : 9, ans = 0;
	for (ll i = 0; i <= mx; i++) ans += dfs(now - 1, (ld && !i) ? 0 : get(val, i), lim && i == mx, ld && !i);
//	printf("%d\n", ans);
	return !lim ? f[now][val] = ans : ans;
}

ll cal(ll x)
{
	ll len = 0;
	while (x) num[++len] = x % 10, x /= 10;
	return dfs(len, 0, 1, 1);
}

inline void solve()
{
	ll a, b;
	scanf("%lld%lld", &a, &b);
	printf("%lld\n", cal(b) - cal(a - 1));
}

signed main()
{
	memset(f, -1, sizeof f);
	ll t;
	scanf("%lld", &t);
	while (t--) solve();
}

原文地址:https://www.cnblogs.com/Chasing-Dreams-Z/p/15172785.html