Live2D

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;
}
posted @ 2020-08-18 20:41  Dark_Romance  阅读(139)  评论(0编辑  收藏  举报