1. 程式人生 > 其它 >2021/01/19訓練總結

2021/01/19訓練總結

文章目錄

前言

昨天進行了一場數位dp專題,寫下題解。如果要學習數位dp,這裡給一個我學習的連結
關於數位dp的一些題:
P4999 煩人的數學作業
P6218 [USACO06NOV] Round Numbers S
P2657 [SCOI2009] windy 數
P4317 花神的數論題

P2602 [ZJOI2010]數字計數
P4127 [AHOI2009]同類分佈
P4124 [CQOI2016]手機號碼

HDU 2089 不要62

題目連結:不要62
題目大意:給定 [ L , R ] [L,R] [L,R]區間,問區間內不含62和4的數字的個數。
資料範圍: 0 < n ≤ m < 1000000 0<n \le m <1000000 0<nm<1000000
題解:這個資料量不是很大,所以可以直接暴力 L → R L\to R LR掃一遍,看是否有62 o r or or 4。不過還是說一下我數位 d p dp dp的做法,其實簡單數位 d p dp dp完全是板子的做法。即先將 [ L , R ] [L,R]

[L,R]變成 [ 1 , R ] − [ 1 − L − 1 ] [1,R]-[1-L-1] [1,R][1L1]。然後用 f i n d ( x ) find(x) find(x)函式來找 [ 1 , x ] [1,x] [1,x]內滿足要求的個數。我們需要在dfs中維護的有:還剩餘的位數,是否頂著上界,搜尋的前一個數,是否已經包含62,是否包含了4。然後我們對應轉移就行了。
AC程式碼:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template
<class T> void read(T& x) { T res = 0, f = 1; char c = getchar(); while (!isdigit(c)) { if (c == '-')f = -1; c = getchar(); } while (isdigit(c)) { res = (res << 3) + (res << 1) + c - '0'; c = getchar(); } x = res * f; } #define int long long const ll N = 200000 + 10; const int mod = 1e9 + 7; int w[15], dp[10][2][10][2][2]; int dfs(int cur, int limit, int pre, bool _4, bool _62) { if (!cur) { if (_4 || _62)return 0; return 1; } if (~dp[cur][limit][pre][_4][_62])return dp[cur][limit][pre][_4][_62]; int ans = 0; int tp = limit ? w[cur] : 9; for (int i = 0; i <= tp; i++) { ans += dfs(cur - 1, limit && i == tp, i, _4 || i == 4, _62 || (i == 2 && pre == 6)); } return dp[cur][limit][pre][_4][_62] = ans; } int find(int x) { memset(dp, -1, sizeof(dp)); w[0] = 0; while (x)w[++w[0]] = x % 10, x /= 10; return dfs(w[0], 1, 0, 0, 0); } signed main() { //ios::sync_with_stdio(false); #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif // ONLINE_JUDGE while (1) { int l, r; read(l), read(r); if (!l && !r)break; printf("%lld\n", find(r) - find(l - 1)); } return 0; }

HDU - 3555 Bomb

題目連結:Bomb
題目大意:求 [ L , R ] [L,R] [L,R]區間內包含 49 49 49的有多少。
資料範圍: 1 ≤ T ≤ 1 0 5 , 1 ≤ N ≤ 2 63 − 1 1 \le T \le 10^5,1\le N \le 2^{63}-1 1T105,1N2631
題解:我們可以發現,不同的數位dp基本上改的就是dfs函式。這題我們就要維護前面的搜尋結果裡面是否已經包含了49,我這裡用 i s o k isok isok表示。然後要維護 i s o k isok isok,我們需要得到上一次的搜尋數位是什麼,我們用 p r e pre pre來記錄。剩下的就是對應轉移了。
AC程式碼:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[30], dp[30][2][10][2],t;
int dfs(int cur, bool limit, int pre, bool isok)
{
	if (!cur)
	{
		return isok;
	}
	if (~dp[cur][limit][pre][isok])return dp[cur][limit][pre][isok];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp),i, isok||(i == 9 && pre==4));
	}
	return dp[cur][limit][pre][isok] = ans;
}
int find(int x)
{
	memset(dp, -1, sizeof(dp));
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 0, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(t);
	while (t--)
	{
		int x; read(x);
		printf("%lld\n", find(x));
	}

	return 0;
}

CodeForces - 55D Beautiful numbers

題目連結:Beautiful numbers
題目大意:求 [ L , R ] [L,R] [L,R]區間內包含可以被自身所有數位整除的有多少。
資料範圍: 1 ≤ t ≤ 10 , 1 ≤ l ≤ r ≤ 9 ∗ 1 0 18 1\le t \le 10,1\le l\le r\le 9*10^{18} 1t10,1lr91018
題解:非常好的一道題,數位dp進階必做。首先我們依靠前兩題來寫一下我們的模板dfs函式

int dfs(int cur, bool limit, int premul, int prenum)
{
	if (!cur)
	{
		return prenum % premul == 0;
	}
	if (~dp[cur][limit][premul][prenum])return dp[cur][limit][premul][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), !i?premul:premul*i, (prenum * 10 + i));
	}
	return  dp[cur][limit][premul][prenum] = ans;
}

和我們之前寫的完全一樣! p r e n u m prenum prenum表示之前搜尋的數, p r e m u l premul premul表示已經搜尋的數位的積。這樣我們就完成了這一題。不過有個問題就是dp陣列的大小。我們 p r e m u l premul premul可以達到 9 ∗ 1 0 18 9*10^{18} 91018的大小!空間已經炸穿了。我們這裡可以發現一個空間小優化就是我們可以預處理出 9 ! 9! 9!,邊模邊運算。dfs如下

int dfs(int cur, bool limit, int premul, int prenum)
{
	if (!cur)
	{
		return prenum % premul == 0;
	}
	if (~dp[cur][limit][premul][prenum] && !limit)return dp[cur][limit][premul][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), !i ? premul : premul * i, (prenum * 10 + i) % (9!));
	}
	return  dp[cur][limit][premul][prenum] = ans;
}

這樣空間要求變成了 19 ∗ 2 ∗ 9 ! ∗ 9 ! 19*2*9!*9! 1929!9!掐指一算,我們還是炸空間了,然後可以可以注意到一個優化的點,就是我們可以將 p r e m u l premul premul變成 p r e l c m prelcm prelcm也就是將之前搜尋到的數位積變成數位lcm。這樣我們就可以將9!優化下到 l c m ( 1 , 2 , . . 9 ) = 2520 lcm(1,2,..9)=2520 lcm(1,2,..9)=2520。dfs如下

int dfs(int cur, bool limit, int prelcm, int prenum)
{
	if (!cur)
	{
		return prenum % prelcm == 0;
	}
	if (~dp[cur][limit][prelcm][prenum] && !limit)return dp[cur][limit][prelcm][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), !i ? prelcm : lcm(prelcm ,i), (prenum * 10 + i) % (9!));
	}
	return  dp[cur][limit][prelcm][prenum] = ans;
}

空間成功變成 19 ∗ 2 ∗ 2520 ∗ 2520 19*2*2520*2520 19225202520。還是超了一點。。。不過我們可以發現 p r e l c m prelcm prelcm無法取到 1 → 2520 1\to 2520 12520之間的所有數,只能取到2520的因子數。所以最後一個優化就是將 p r e l c m prelcm prelcm離散化。離散化後可以發現 p r e l c m prelcm prelcm只能取48個,所以我們空間變成了 19 ∗ 2 ∗ 50 ∗ 2520 19*2*50*2520 192502520,完全ok了。

AC程式碼:


#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
int w[30], dp[20][50][2600], t, totmod;
int a[N], toa[N];
int gcd(int x, int y)
{
	return !y ? x : gcd(y, x % y);
}
int lcm(int x, int y)
{
	if (!x || !y)return x + y;
	return x * y / gcd(x, y);
}
void init()
{
	memset(dp, -1, sizeof(dp));
	totmod = 1;
	for (int i = 2; i <= 9; i++)totmod = lcm(totmod, i);
	for (int i = 1; i <= totmod; i++)
	{
		if (totmod % i == 0)a[++a[0]] = i, toa[i] = a[0];
	}
}
int dfs(int cur, bool limit, int prelcm, int prenum)
{
	if (!cur)
	{
		return prenum % prelcm == 0;
	}
	if (~dp[cur][toa[prelcm]][prenum]&&!limit)return dp[cur][toa[prelcm]][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), lcm(prelcm, i), (prenum * 10 + i) % totmod);
	}
	if (!limit)dp[cur][toa[prelcm]][prenum] = ans;
	return  ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 1, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	init();
	read(t);
	while (t--)
	{
		int l, r; read(l), read(r);
		printf("%lld\n", find(r) - find(l - 1));
	}

	return 0;
}

LightOJ - 1336 Sigma Function

題目連結:Sigma Function
題目大意:給定n,求[1-n]中因子個數為偶數的個數。
資料範圍: 1 ≤ t ≤ 100 , 1 ≤ n ≤ 1 0 12 1\le t \le 100,1\le n\le10^{12} 1t100,1n1012
題解:我是直接打表寫的。程式如下

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int calc(int x)
{
	int ans = 0;
	for (int i = 1; i * i <= x; i++)
	{
		if (x % i == 0)
		{
			ans += i;
			if (x != i * i)ans += x / i;
		}
	}
	return ans;
}
ll t, n;
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	for (int i = 1; i <= 1000; i++)
	{
		if(calc(i)%2)
			printf("%d ", i);
	}

	return 0;
}

結果如下:
在這裡插入圖片描述
我發現因子個數為奇數的i都滿足 i = 2 p ∗ x ∗ x i=2^p*x*x i=2pxx
進一步分解,假設p是偶數則有 y = x ∗ 2 p 2 , i = y ∗ y y=x*2^{\frac{p}{2}},i=y*y y=x22p,i=yy
p是奇數則有 y = x ∗ 2 p 2 , i = 2 ∗ y ∗ y y=x*2^{\frac{p}{2}},i=2*y*y y=x22p,i=2yy
即如果i滿足是完全平方數 o r or or完全平方數的兩倍則i的因子個數數是奇數。
又完全平方數的個數= n \sqrt{n} n ,完全平方數的兩倍個數= n 2 \sqrt{\frac{n}{2}} 2n 。所以 a n s = n − n − n 2 ans=n-\sqrt{n}-\sqrt{\frac{n}{2}} ans=nn 2n
這裡有一個證明的題解:連結
AC程式碼:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
ll t, n;
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE

	ll t, n; int qiu = 0;
	read(t);
	while (t--)
	{
		read(n);
		printf("Case %d: %lld\n", ++qiu, n - (ll)sqrt(n) - (ll)sqrt(n / 2));
	}
	return 0;
}

HDU - 4352 XHXJ’s LIS

題目連結:C - XHXJ’s LIS
題目大意:給定[L,R],求各位數字組成的嚴格上升子序列的長度為K的個數。
資料範圍: 1 ≤ L ≤ R ≤ 2 63 − 1 , 1 ≤ K ≤ 10 1\le L\le R\le 2^{63}-1,1\le K \le 10 1LR2631,1K10
題解:我們用一個10位二進位制狀態來表示最長上升子序列,狀態的修改參考導彈攔截題目裡面給出的 n l o g n nlogn nlogn修改的方法。其他的就是數位dp了。
AC程式碼:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int t, l, r, K;
int dp[20][2][1 << 11][11], w[21];
int getnum(int x)
{
	int ans = 0;
	while (x)
	{
		ans++;
		x -= x & -x;
	}
	return ans;
}
int updatestate(int x, int st)
{
	for (int i = x; i <= 9; i++)
	{
		if (st & (1 << i))return (st ^ (1 << i)) | (1 << x);
	}
	return st | (1 << x);
}
int dfs(int cur, int limit, int _0, int state)
{
	if (!cur)return getnum(state) == K;
	if (!limit && ~dp[cur][_0][state][K])return dp[cur][_0][state][K];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && i == tp, _0 && i == 0, (_0 && i == 0) ? 0 : updatestate(i, state));
	}
	if (!limit)dp[cur][_0][state][K] = ans;
	return ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 1, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	memset(dp, -1, sizeof(dp));
	read(t); int qiu = 0;
	while (t--)
	{
		read(l), read(r), read(K);
		printf("Case #%lld: %lld\n", ++qiu, find(r) - find(l - 1));
	}

	return 0;
}

LightOJ - 1282 Leading and Trailing

題目連結:Leading and Trailing
題目大意:求 n k n^k nk的前3位和後3位
資料範圍: 2 ≤ n ≤ 2 31 , 1 ≤ k ≤ 1 0 7 2\le n \le 2^{31},1\le k \le 10^7 2n231,1k107
題解:參考連結
後三位我們可以直接快速冪模1000求出,對於前三位
1 0 p = n k 10^p=n^k 10p=nk其中 p = l o g 10 ( n k ) = k ∗ l o g 10 ( n ) = m + x p=log10(n^k)=k*log10(n)=m+x p=log10(nk)=klog10(n)=m+x, m m m p p p的整數部分, x x x p p p的小數部分。寫成科學計數法就是 1 0 x ∗ 1 0 m = n k 10^x*10^m=n^k 10x10m=nk其中 1 0 x 10^x 10x大於1小於10, m m m是決定值的位數。也就是我們的前3位只和 1 0 x 10^x 10x有關, 1 0 x ∗ 100 10^x*100 10x100再取整就是答案了。
AC程式碼:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod =1000;
ll t, n, k;
int fpow(ll x, ll y)
{
	x %= mod;
	int ans = 1;
	while (y)
	{
		if (y & 1)ans = 1ll * x * ans % mod;
		x = 1ll * x * x % mod; y >>= 1;
	}
	return ans;
}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(t); int qiu = 0;
	while (t--)
	{
		read(n), read(k);
		double p = k * log10(n);
		double x = p - int(p);
		double tx = pow(10, x);
		printf("Case %d: %03d %03d\n", ++qiu, (int)(tx * 100), fpow(n, k));
	}

	return 0;
}