Loading...

windy数

看到一些dalao写的不浅显易懂,我来补一发较为详细的代码

思想

  • 1.有前缀和的思想

即,我们在\(b\)那个位置,求出一个1到\(b\)区间的数目

然后在 \(a-1\)那里,进行同样的操作,进行运算,

最后的答案就是solve(b) - solve(a-1)

  • 2.这道题是一个数位DP

通常数字都很大,我们可以把他们存到一个数组里,这样访问的时候,访问数组里的数就好了,很方便的(详见代码)

  • 3.我们拆分完成后,就到了展示高端操作的时候

蒟蒻觉得那些dalao写的过于抽象,便使用了记忆化搜索

(其实记忆化搜索与循环嵌套做出来的dp时间复杂度是一样的,只不过,记忆化搜素可能会比较直观,但代码量长一些)

先把这个dfs的代码贴出来

dfs中有四个参数

pos表示当前搜索到哪一位

pre表示前驱,即自己的上一位是谁

flg表示当前所搜到的这种情况,其数字是否能够与原先的数匹配上

举个例子,一个数123456,我们枚举到1234,这是的flg就为1

zero的含义有两种,是相反的,我都有AC代码,大家可以去看

第一种是zero为1,表示没有前导零,反过来,zero为零,表示有前导零(我下面的dfs就是用的这个含义,刚刚进入的时候,为dfs(当前num的位数,0, 1, 0) )

第二种是zero为0,表示有前导零,反过来,zero为0,表示没有前导零,这时“初代”dfs的参数分别为(当前num的位数,0, 1, 1),当然二者还有一些细节在代码中

\(f[i][j]\)表示搜到第\(i\)位,在前驱是\(j\)的情况下,的最多方案数目


inline int dfs (int pos, int pre, int flg, int zero) {
    if (!pos) return 1;//我位数为零了,自然就return了
    //有同学要问了,为什么pos等于零了,我们就可以返回1?
    //我们的dfs每一次搜索,都是在保证合法的情况下
    //如果某种情况不合法,是根本不会进入到pos为零这个阶段
	if (!flg && f[pos][pre]!=-1&&zero) return f[pos][pre];
    //典型的记忆化,如果f数组以前更新过,且当前没有前导零,我们下一次拿来直接用就好了
	int net, tot(0);
    /*
    tot是来记录答案的,net是来抉择自己下一个可以选成什么
    这里有这么一个坑点,假如原数是123456
    我们当前搜到123,那继续搜的话,后三位是不是一定要小于456?
    因为我们不可能比原数还要大对吧
    而假如我们搜到122,那后面我们即使是999,是不是依旧小于原数123456?
    (姑且不考虑搜到的这个情况是否合法)
    
    来看代码,当flg等于1,意味着前边的数,和自己已经搜到的一样,
    那么我们下一位可选的最大数是不是就是原数中的那一位?
    同样,我们举个例子(都是在默认原数是123456的前提下)
    当前搜到123,发现flg等于一(因为123456的前三位与123的前三位是相匹配的)
    那我们的net(上限)最大为原数中的那一位,在这里是4
    
   如果当前搜到122,发现flg等于零,
   意味着自己与原数不一样
   那既然不一样,那一定是之前的某个高位比原数小,
   既然高位都比原数小了,后边无论怎么操作,都不会大于原数
   而每一位数字的上限是9,我们给它定位最大值9就可以了
    */
	if (flg) net = num[pos];
	else net = 9;
	for (int i = 0; i <= net; ++ i) {
		int cha = abs (pre-i);
        	//找差值,判断
		if (cha<2 && zero);
        	/*注意我这里有一个分号
            即if (cha<2 && zero)成立之后,不会进行任何操作
            这个if是说,差值小于2,并且前边没有前导零
            判断有没有前导零的判断很关键
            我们假定搜到一个数1 其实能这个1实际上是“00001”(前面具体多少个零我也不知道)
            而我们来看 501
           	抛开这个if不说,我们知道501不是windy数,而1是.那我们回归到if上
            001和501同时满足cha < 2,但是对于001却不满足zero为1的情况,所以001不会进这个if而501会进入,501就被“过滤”了
            这是最简单的一种情况
	        */
		else if (i == net&&flg) tot += dfs (pos-1, i, 1, 1);
        /*
        if (i==net&&flg)
        这是在判断,下一位要选的数是不是到达了上限,且是否与原数匹配
        如果发现已经到了上限(我们姑且不论那个上限是9还是原数中的某一位)
       	那我们可以继续搜下一层,位数要减1,前驱即为i,flg为1,
        而最后的参数zero为什么会是1呢?
        大家来想一下,首先我们的原数(最开始被拆分的那个数)非零
        而我们现在枚举到的这个数,能与原数匹配
        那么这个被枚举(或者叫“搜索”)到的数,就一定也非零
        自然,zero为1
        (zero的含义为第一种,不清楚的自己去上面扒拉)
        */
		else {
			if (i!=0 || zero) tot += dfs (pos-1, i, 0, 1);
            /*
            这个if中是“或”的条件
            即只要满足当前枚举到的下一位不是0,或者zero为1(即表示没有前导零)就可以继续跑dfs
            这里有个问题,flg为什么是0.大家来考虑i这个循环
            i是从1循环到上限net的,我们前边提到过,net只能是9或者原数中的一位
            假如net为9,那么我们的flg已经是0了,
            因为net等于9其成立条件就是flg为零
            假如net为原数中一位,那假如我们当前枚举的下一位不是原数中的那个数(上限)(net)
            那我们自然是flg为0,假如到了上限,并且flg前面是1,(这是上一个if干的活了)
            */
			else tot += dfs (pos-1, i, 0, 0);
           	/*
            这个else成立的条有两个
            第一个,i不等于0;第二个,zero是零
            就是说,当前要枚举的下一位是0,并且自己前边是有前导零的,所以dfs的参数zero才为0
            */
		}
	}
	if (!flg && zero) f[pos][pre] = tot;
    	//保存答案,方便以后使用
        //这里对应上面的记忆化,在一定条件下时记录,保证一致性
	return tot;
}

下面贴一下完整代码

zero第一种含义的代码

/*
By TheStars
Time:2020.03.09
*/
#include <bits/stdc++.h>
#define N 330
using namespace std;
int a, b, cnt;
int num[N];
int f[N][N];

inline int dfs (int pos, int pre, int flg, int zero) {
    if (!pos) return 1;
	if (!flg && f[pos][pre]!=-1&&zero) return f[pos][pre];
	int net, tot(0);
	if (flg) net = num[pos];
	else net = 9;
	for (int i = 0; i <= net; ++ i) {
		int cha = abs (pre-i);
		if (cha<2 && zero);
		else if (i == net&&flg) tot += dfs (pos-1, i, 1, 1);
		else {
			if (i!=0 || zero) tot += dfs (pos-1, i, 0, 1);
			else tot += dfs (pos-1, i, 0, 0);
		}
	}
	if (!flg && zero) f[pos][pre] = tot;
	return tot;
}

inline int work (int x) {
	memset (f, -1, sizeof f);
	cnt = 0;
	while (x) num[++cnt] = x%10, x /= 10;
	return dfs (cnt, 0, 1, 0);
}

int main (){
	scanf ("%d%d", &a, &b);
	int one = work (b);
	int two = work (a-1);
	cout << one - two;
	fclose (stdin), fclose (stdout);
	return 0;
}

zero第二种含义的代码

/*
By TheStars
Time:2020.03.09
*/
#include <bits/stdc++.h>
#define N 330
using namespace std;
int a, b, cnt;
int num[N];
int f[N][N];

inline int dfs (int pos, int pre, int flg, int zero) {
    if (!pos) return 1;
	if (!flg && f[pos][pre]!=-1&&!zero) return f[pos][pre];
    int net, tot(0);
	if (flg) net = num[pos];
	else net = 9;
	for (int i = 0; i <= net; ++ i) {
		int cha = abs (pre-i);
		if (cha<2 && !zero);
		else if (i == net&&flg) tot += dfs (pos-1, i, 1, 0);
		else {
			if (i!=0 || !zero) tot += dfs (pos-1, i, 0, 0);
			else tot += dfs (pos-1, i, 0, 1);
		}
	}
	if (!flg && !zero) f[pos][pre] = tot;
	return tot;
}

inline int work (int x) {
	memset (f, -1, sizeof f);
	cnt = 0;
	while (x) num[++cnt] = x%10, x /= 10;
	return dfs (cnt, 0, 1, 1);
}

int main (){
	scanf ("%d%d", &a, &b);
	int one = work (b);
	int two = work (a-1);
	cout << one - two;
	fclose (stdin), fclose (stdout);
	return 0;
}
posted @ 2020-07-23 12:26  Youngore  阅读(216)  评论(0)    收藏  举报