把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【数位dp】SCOI2009windy数 纪中集训提高B组

题目传送门
Time Limits: 1000 ms Memory Limits: 65536 KB Detailed Limits

Description

windy定义了一种windy数。
不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。
windy想知道,在A和B之间,包括A和B,总共有多少个windy数?

Input

两个整数,A B。

Output

一个整数,表示A~B中有多少个windy数。

Sample Input

1 10

Sample Output

9

Hint

100%的数据,满足 1 <= A <= B <= 2000000000 。


其实这是一道非常裸的数位dp。
可是考场上没有意识到,只是觉得这是一道打表好题(雾)。
后来想起数位dp还是吴老师亲自讲的(对不起他老人家),还做了这道经典题目,还在VJ上A过:
不要62

#include<cstdio>
#include<cstring>
using namespace std;
int f[8][2],digit[8];
/*
f[i][j]: 
i:位数 
j==0:尾数不为6 
j==1:尾数为6
*/
int dfs(int pos,bool six,bool lim)
{//位数(从高到低),尾数是否为6,是否是上限
	if(pos<=0) return 1;
	if(!lim&&f[pos][six]!=-1) return f[pos][six];
	int num/*这一位的最大值*/,ans=0;
	if(lim) num=digit[pos];
		else num=9;
	for(int i=0;i<=num;i++)//可以从0开始枚举,表示这一位没有,比如三位数的百位为0就是两位数
	{
		if(i==4||(six&&i==2)) continue;
		ans+=dfs(pos-1, i==6,lim&&i==num);
		//这一位的前面的位数如果都已经取到了上界,该位就有限制,否则没有
	}
	if(!lim) f[pos][six]=ans;
	return ans;
}
int solve(int N)
{
	int len=0;
	memset(f,-1,sizeof(f));
	memset(digit,0,sizeof(digit));
	while(N)
	{
		digit[++len]=N%10;
		N/=10;
	}
	return dfs(len,0,1);//从高位到低位
}
int main()
{
	int A,B;
	while(scanf("%d %d",&A,&B)!=EOF&&A&&B)
		printf("%d\n",solve(B)-solve(A-1));
	return 0;
}
/*
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 15
int f[MAXN][MAXN][3],digit[MAXN];
/*
f[i][j][k]: 
i:位数 
j:余数 
k==0: 不含13且不以1结尾
k==1: 不含13且以1结尾
k==2: 含13

int dfs(int pos,int mod,int kk,bool lim)
{//位数,余数,状态(意义同f数组的k维),是否是上限
	if(pos<=0) return mod==0&&kk==2;
	if(!lim&&f[pos][mod][kk]) return f[pos][mod][kk];
	int num,ans=0;
	if(lim) num=digit[pos];
	else num=9;
	for(int i=0;i<=num;i++)
	{
		int modn=(mod*10+i)%13;//传到下一位的余数
		int kkn=kk;
		if(kk==0&&i==1) kkn=1;
		if(kk==1&&i!=1) kkn=0;
		if(kk==1&&i==3) kkn=2;
		ans+=dfs(pos-1,modn,kkn,lim&&i==num);
	}
	if(!lim) f[pos][mod][kk]=ans;
	return ans;
}

int main()
{
	int N;
	while(scanf("%d",&N)!=EOF)
	{
		int len=0;
		memset(f,-1,sizeof(f));
		memset(digit,0,sizeof(digit));
		while(N)
		{
			digit[++len]=N%10;
			N/=10;
		}
		printf("%d\n",dfs(len,0,0,1));
	}
	return 0;
}*/

被注释的代码似乎是另外一道不要13的题(雾),而且运行了一下还发现我自己写炸了?挖坑待填。

好吧,还是先放这道题的正解(这篇博客存在的意义是什么?)
其实还想写个dfs版本的,继续挖坑待填。

//#pragma GCC optimize(2)
#include<queue>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 20
#define INF 0x3f3f3f3f
#define ll long long
int a,b;
ll dp[N][N],c[N];
//长度为i中最高位是j的windy数的个数
//注意控制最高位不能为0 
int Abs(int x)
{
	if(x>=0) return x;
	return -x;
}
void Init()
{
	for(int i=0;i<=9;i++)
		dp[1][i]=1;
	for(int i=2;i<=10;i++)//枚举位数
		for(int j=0;j<=9;j++)
			for(int k=0;k<=9;k++)
				if(Abs(j-k)>=2) dp[i][j]+=dp[i-1][k];
}
ll lyt(int x)
{
	memset(c,0,sizeof(c));
	int len=0;ll ans=0;
	while(x)
	{
		c[++len]=x%10;
		x/=10;
	}
	for(int i=1;i<=len-1;i++)//长度小于len的windy数
		for(int j=1;j<=9;j++)//控制最高位不为0
			ans+=dp[i][j];
	for(int i=1;i<c[len];i++)//长度为len 最高位小于它
		ans+=dp[len][i];
	//长度为len 前i-1位(下标从i+1~len)都与原数相同 
	for(int i=len-1;i>=1;i--)//枚举第几位 
	{
		for(int j=0;j<=c[i]-1;j++)//枚举第i位上是什么
			if(Abs(j-c[i+1])>=2)//与上一位相差2以上
				ans+=dp[i][j];
		if(Abs(c[i+1]-c[i])<2)//若满足差小于2 windy数的第i位不能与原数的第i位相同 
			break;
	}
	//lyt()处理的是开区间 不会算上x它自己 因为上面的枚举到最后
	//只会算到十位~最高位与原数相同,个位枚举又是从原数那一位上-1开始枚举的
	//(因为要避免重复 如果那里的枚举取了等号 就与最外层循环枚举的前i-1位都与原数相同重复) 
	return ans;
}
int main()
{
	Init();
    scanf("%d %d",&a,&b);
    printf("%lld\n",lyt(b+1)-lyt(a));
    //这里lyt()处理的是开区间 原因见51行注释 
    return 0;
}
posted @ 2019-08-11 20:25  Starlight_Glimmer  阅读(9)  评论(0编辑  收藏  举报  来源
浏览器标题切换
浏览器标题切换end