1. 程式人生 > 其它 >暑期專題五 數位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();
}