讲数位dp,然后发现自己学暴了,这里挑几道有意思的题目记录一下,以免将来死得太惨。
windy数
题目大意
定义 windy 数为满足相邻两位差值 (le 2) 的数,给出 (l,r) ,求出 ([l,r]) 内有多少 windy 数。
(l,rle 2 imes 10^9)
思路
我™ 这样一个板子题调了一个小时,果然是我自己菜爆了。。。
我们可以设 (dp[i][x]) 表示确定了前 (i) 位并且第 (i) 位为 (x) 时的合法方案数,然后直接记忆化搜索就好了。下面是一些数位 dp 的细节
-
注意前面几位跟极限相同的情况需要特殊判断
-
还有前导零的情况需要特殊判断
原因就是这两种情况会限制后面几位的选择。
( exttt{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 ('
');
return 0;
}
恨 7 不成妻
题目大意
定义一个数字与 (7) 有关当且仅当一下几种情况:
-
数字中含有 (7)
-
各位数字之和为 (7) 的倍数
-
为 (7) 的倍数
给出 ([l,r]) ,求出该区间内与 (7) 无关的数字的平方和。
(lle rle 10^{18}),答案对 (10^9+7) 取模。
思路
显然我们没有办法直接搞了。但是我们发现假设当前位为 (x) ,后面为 (y) ,那么答案就是 ((x+y)^2=x^2+2xy+y^2) ,然后我们发现 ( ext{answer} =x^2 imes sum+ 2x imes y+sum y^2) ,然后我们直接维护合法方案数、合法方案的数字和、合法数字的平方和即可。
( exttt{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 ('
');
}
return 0;
}
tickets
题目大意
给出 (l,r,k) ,将 ([l,r]) 划分成某些段,使得每一段上面的编号的每位数字之和不小于 (k) 并且尽可能小。求出分成的段数。
(lle rle 10^{18},kle 1000)
思路
为了更好帮助理解题意,比如 (l=40,k=11) ,那么 (40,41,42) 就会被划分成一段,因为 (4+0+4+1+4+2ge 11) 而且 (4+0+4+1<11)。
私认为是很巧妙的一道题。我们可以考虑设 (dp[i][s1][s2]) 表示考虑第 (i) 位当前编号,(s1) 为当前编号产生的每位数字之和,(s2) 表示已经划分出来的贡献。然后我们就可以进行合并,具体见代码。
( exttt{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 ('
');
return 0;
}