1. 程式人生 > 實用技巧 >2020-08-18 集訓題目題解

2020-08-18 集訓題目題解

講數位dp,然後發現自己學暴了,這裡挑幾道有意思的題目記錄一下,以免將來死得太慘。

windy數

題目傳送門

題目大意

定義 windy 數為滿足相鄰兩位差值 \(\le 2\) 的數,給出 \(l,r\) ,求出 \([l,r]\) 內有多少 windy 數。

\(l,r\le 2\times 10^9\)

思路

我™ 這樣一個板子題調了一個小時,果然是我自己菜爆了。。。

我們可以設 \(dp[i][x]\) 表示確定了前 \(i\) 位並且第 \(i\) 位為 \(x\) 時的合法方案數,然後直接記憶化搜尋就好了。下面是一些數位 dp 的細節

  • 注意前面幾位跟極限相同的情況需要特殊判斷

  • 還有前導零的情況需要特殊判斷

原因就是這兩種情況會限制後面幾位的選擇。

\(\texttt{Code}\)

#include <bits/stdc++.h>
using namespace std;

#define Int register int
#define MAXN  

template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

int len,nnum[11],dp[11][10];

int Abs (int x){return x < 0 ? -x : x;}

int dfs (bool up,bool zero,int now,int x){
	if (!now) return 1;	
	if (!up && zero && ~dp[now][x]) return dp[now][x];
	int res = 0,upup = up ? nnum[now] : 9;
	for (Int i = 0;i <= upup;++ i)if (Abs (x - i) >= 2 || !zero) res += dfs (up & (i == upup),zero || i,now - 1,i);
	if (!up && zero) dp[now][x] = res;
	return res;
}

int calc (int n){
	int tmp = n,res = 0;len = 0;
	while (tmp) nnum[++ len] = tmp % 10,tmp /= 10;
	memset (dp,-1,sizeof (dp));
	return dfs (1,0,len,-2);
}

signed main(){
	int a,b;read (a,b);
	write (calc (b) - calc (a - 1)),putchar ('\n');
	return 0;
}

恨 7 不成妻

題目傳送門

題目大意

定義一個數字與 \(7\) 有關當且僅當一下幾種情況:

  • 數字中含有 \(7\)

  • 各位數字之和為 \(7\) 的倍數

  • \(7\) 的倍數

給出 \([l,r]\) ,求出該區間內與 \(7\) 無關的數字的平方和。

\(l\le r\le 10^{18}\),答案對 \(10^9+7\) 取模。

思路

顯然我們沒有辦法直接搞了。但是我們發現假設當前位為 \(x\) ,後面為 \(y\) ,那麼答案就是 \((x+y)^2=x^2+2xy+y^2\) ,然後我們發現 \(\text{answer} =x^2\times \sum+ 2x\times y+\sum y^2\)

,然後我們直接維護合法方案數、合法方案的數字和、合法數字的平方和即可。

\(\texttt{Code}\)

#include <bits/stdc++.h>
using namespace std;

#define Int register int
#define mod 1000000007
#define int long long

template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

int len,pw[19],nnum[19];

int mul (int a,int b){return 1ll * a * b % mod;}void Mul (int &a,int b){a = mul (a,b);}
int dec (int a,int b){return a >= b ? a - b : a + mod - b;}void Dec (int &a,int b){a = dec (a,b);}
int add (int a,int b){return a + b >= mod ? a + b - mod : a + b;}void Add (int &a,int b){a = add (a,b);}

struct node{
	int cnt,sum,sqr;bool exi;//分別維護0次方、1次方、2次方,是否訪問過 
	void clear (){cnt = sum = sqr = exi = 0;}
}dp[19][7][7];

node dfs (bool lim,int now,int sum1,int sum2){
	if (!now){
		node tmp;tmp.clear();
		if (sum1 && sum2) tmp.cnt = 1;
		return tmp;
	}
	if (!lim && dp[now][sum1][sum2].exi) return dp[now][sum1][sum2];
	int up = lim ? nnum[now] : 9;node res;res.clear();
	for (Int i = 0;i <= up;++ i){
		if (i == 7) continue;
		int tmp = mul (pw[now - 1],i);
		node nxt = dfs (lim & (i == up),now - 1,(sum1 + i) % 7,(sum2 * 10 + i) % 7);
		Add (res.cnt,nxt.cnt),Add (res.sum,add (nxt.sum,mul (tmp,nxt.cnt))),Add (res.sqr,add (mul (nxt.cnt,mul (tmp,tmp)),add (mul (2 * tmp,nxt.sum),nxt.sqr)));
	}
	if (!lim) dp[now][sum1][sum2] = res,dp[now][sum1][sum2].exi = 1;
	return res;
}

int calc (int n){
	memset (dp,0,sizeof (dp));
	int tmp = n;len = 0;
	while (tmp) nnum[++ len] = tmp % 10,tmp /= 10;
	return dfs (1,len,0,0).sqr; 
}

signed main(){
	int T;read (T);
	pw[0] = 1;for (Int i = 1;i <= 18;++ i) pw[i] = mul (pw[i - 1],10);
	while (T --> 0){
		int l,r;read (l,r);
		write (dec (calc (r),calc (l - 1))),putchar ('\n');
	} 
	return 0;
}

tickets

題目傳送門

題目大意

給出 \(l,r,k\) ,將 \([l,r]\) 劃分成某些段,使得每一段上面的編號的每位數字之和不小於 \(k\) 並且儘可能小。求出分成的段數。

\(l\le r\le 10^{18},k\le 1000\)

思路

為了更好幫助理解題意,比如 \(l=40,k=11\) ,那麼 \(40,41,42\) 就會被劃分成一段,因為 \(4+0+4+1+4+2\ge 11\) 而且 \(4+0+4+1<11\)

私認為是很巧妙的一道題。我們可以考慮設 \(dp[i][s1][s2]\) 表示考慮第 \(i\) 位當前編號,\(s1\) 為當前編號產生的每位數字之和,\(s2\) 表示已經劃分出來的貢獻。然後我們就可以進行合併,具體見程式碼。

\(\texttt{Code}\)

#include <bits/stdc++.h>
using namespace std;

#define Int register int
#define int long long

template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

int l,r,K,tmp,lnum[19],rnum[19];

struct node{
	int a,b;bool exi;
	node (){a = b = exi = 0;}
	node (int _a,int _b,bool _exi){a = _a,b = _b,exi = _exi;}
}dp[20][185][1010];

void Merge (node &x,node y){
	x.a += y.a,x.b = y.b;
}

node dfs (bool lim1,bool lim2,int now,int sum,int rem){
	node ans = node (0,rem,0);
	if (dp[now][sum][rem].exi && !lim1 && !lim2) return dp[now][sum][rem];
	if (!now){
		if (sum + rem >= K) ans = node (1,0,0);
		else ans = node (0,sum + rem,0); 
	}
	else{
		int down = lim1 ? lnum[now] : 0,up = lim2 ? rnum[now] : 9;
		for (Int i = down;i <= up;++ i) Merge (ans,dfs (lim1 & (i == down),lim2 & (i == up),now - 1,sum + i,ans.b));
	}
	if (!lim1 && !lim2) dp[now][sum][rem] = ans,dp[now][sum][rem].exi = 1;
	return ans;
}

signed main(){
	read (l,r,K);
	tmp = l;int len1 = 0;while (tmp) lnum[++ len1] = tmp % 10,tmp /= 10;
	tmp = r;int len2 = 0;while (tmp) rnum[++ len2] = tmp % 10,tmp /= 10;
	write (dfs (1,1,18,0,0).a),putchar ('\n');
	return 0;
}