【数位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;
}
转载请注明出处,有疑问欢迎探讨
博主邮箱 2775182058@qq.com