zoukankan      html  css  js  c++  java
  • 2020-08-18 集训题目题解

    讲数位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;
    }
    
  • 相关阅读:
    利用 FFmpeg 和 ImageMagick, AVI 转 GIF(不失真)
    TinyMCE textarea 输入框外部程序动态修改方法
    eclipse快速向下复制行
    ${factoryList }后面有空格不影响
    pre标签
    js备忘录_2
    eclipse 中 大小写切换:ctrl+shift+x 转为大写 ctrl+shift+y 转为小写
    js备忘录_1
    缓存
    myeclipse bug
  • 原文地址:https://www.cnblogs.com/Dark-Romance/p/13525851.html
Copyright © 2011-2022 走看看