https://www.luogu.org/problemnew/show/P2657

不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。

这道题是个显然到不能再显然的数位dp了。

来个最神奇的dp[i][j]表示i位数,开头为j的windy数的个数吧。

那么dp[i][j]的求法是很显然的,写个sum数组求和更为方便。

那么怎么统计windy数呢?

约定由布丁酱写的数位dp,求闭区间[l,r]时,使用count(r)-count(l-1),也就是count(x)表示不小于的[0,x]中windy数的个数。

怎么求count呢,最简单的思路就是分类讨论。

对最高位分三类:

  最高位为0,遍历所有的dp[i][1~9]再加上0本身。

  最高位为非0且比x最高位小,后面满足条件的可以随意取。

  最高位相等,交给下一位去做。

对非最高位分两类:

  该位在受前面位的限制下任取,后面满足条件的任取。

  该位相等,交给下一位去做。(小心该位相等时非法,找了很久!

最后补上x本身的判断。

最后要小心爆int……

意思是说,在数位dp的时候,每一步分类要不重不漏,每一步要判断合法。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll dp[][]; ll sum(int i,int j1,int j2){
/*if(i==0)
return 1;*/
if(j1<)
j1=;
if(j2>)
j2=;
ll res=;
for(int j=j1;j<=j2;j++){
res+=dp[i][j];
}
return res;
} void init(){
for(int j=;j<=;j++)
dp[][j]=;
for(int i=;i<=;i++){
for(int j=;j<=;j++){
dp[i][j]=sum(i-,,j-)+sum(i-,j+,);
}
} /*for(int j=1;j<=2;j++){
dp[9][j]=sum(8,0,j-2)+sum(8,j+2,9);
}*/ /*for(int i=1;i<=9;i++){
for(int j=0;j<=9;j++){
printf("dp[%d][%d]=%d\n",i,j,dp[i][j]);
}
printf("\n");
}*/
} ll A,B;
int digit[]; ll count(ll x){
//cout<<"x="<<x<<endl;
if(x==)
return ;
//否则岂不是0位数?
ll k=,cx=x;
digit[k++]=;
//占位用的
while(cx){
digit[k++]=cx%;
cx/=;
}
k--;
digit[k+]=;
//也是占位 ll res=;
for(int i=k;i>=;i--){
//printf("res=%d\n",res);
if(i==k){
//最高位取0
for(int ii=i-;ii>=;ii--)
res+=sum(ii,,);
res+=;//0
//其实不用特判啊
for(int j=;j<digit[i];j++){
//最高位比digit小且不为0
if(i->=){
res+=sum(i-,,j-)+sum(i-,j+,);
}
else{
res+=;
}
}
//最高位就取相等,问题留给下一次循环
}
else{
//前面的位都相等,非最高位的情况,双重受限
for(int j=;j<digit[i];j++){
if(abs(digit[i+]-j)<=)
;//被前一位限制取值
else if(i->=){
res+=sum(i-,,j-)+sum(i-,j+,);
}
else{
res+=;
}
}
}
} for(int i=;i<=k;i++){
if(abs(digit[i]-digit[i-])<=){
//printf("res1=%d\n",res);
return res;
}
}
//printf("res2=%d\n",res+1);
return res+;
} int main(){
init();
while(~scanf("%lld%lld",&A,&B)){
printf("%lld\n",count(B)-count(A-));
}
}

后来学会了另一种写法。搜索写法这种写法里要用到lead来指定new_state1的状态。

#include<bits/stdc++.h>
using namespace std;
#define ll long long int a[];
ll dp[][/*前一位的取值,只有0~9*/];//不同题目状态不同
ll dfs(int pos,int state1/*前一位的取值,只有0~9,-1表示不受限制*/,bool lead/*这一位的前面是否为零*/,bool limit/*这一位是否取值被限制(也就是上一位没有解除限制)*/)
//不是每个题都要处理前导零
{
//递归边界,最低位是0,那么pos==-1说明这个数枚举完了
if(pos==-)
return ;/*这里返回1,表示枚举的这个数是合法的,那么这里就需要在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。 */
//第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
if(!limit && !lead && dp[pos][state1]!=-)
return dp[pos][state1];
/*常规写法都是在没有限制的条件记忆化,这里与下面记录状态对应*/
int up=limit?a[pos]:;//根据limit判断枚举的上界up
ll ans=;
//开始计数
for(int i=; i<=up; i++) { //枚举,然后把不同情况的个数加到ans就可以了
int new_state1=i;
if(lead==true&&i==){
//这一位继续是前导0,new_state1应为-1
new_state1=-;
}
if(state1>=&&abs(new_state1-state1)<)
continue;
/*
计数的时候用continue跳过不合法的状态,不再搜索
*/ //合法的状态向下搜索
ans+=dfs(pos-,new_state1,lead && i==,limit && i==a[pos]);//最后两个变量传参都是这样写的
}
//计算完,记录状态
if(!limit && !lead)
dp[pos][state1]=ans;
/*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
return ans;
} ll solve(ll x) {
//可能需要特殊处理0或者-1
if(x<=)
return ; int pos=;
while(x) { //把数位分解
a[pos++]=x%;//编号为[0,pos),注意数位边界
x/=;
} return dfs(pos-/*从最高位开始枚举*/,-/*表示这一位前面并没有对其限制的高位*/,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
} int main() {
memset(dp,-,sizeof(dp));
//一定要初始化为-1 ll le,ri;
while(~scanf("%lld%lld",&le,&ri)) {
printf("%lld\n",solve(ri)-solve(le-));
}
}

洛谷 - P2657 - windy数 - 数位dp的更多相关文章

  1. 洛谷P2657 windy数 [SCOI2009] 数位dp

    正解:数位dp 解题报告: 传送门! 这题一看就是个数位dp鸭,"不含前导零且相邻两个数字之差至少为2"这种的 然后就直接套板子鸭(板子戳总结,懒得放链接辣QAQ 然后就是套路 然 ...

  2. 洛谷P2657 windy数

    传送 裸的数位dp 看这个题面,要求相邻两个数字之差至少为2,所以我们记录当前填的数的最后一位 同时要考虑毒瘤的前导0.如果填的数前面都是0,则这一位填0是合法的. emmm具体的看代码叭 #incl ...

  3. 洛谷P2657 [SCOI2009]windy数 [数位DP,记忆化搜索]

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

  4. luogu P2657 [SCOI2009]windy数 数位dp 记忆化搜索

    题目链接 luogu P2657 [SCOI2009]windy数 题解 我有了一种所有数位dp都能用记忆话搜索水的错觉 代码 #include<cstdio> #include<a ...

  5. P2657 [SCOI2009]windy数 数位dp

    数位dp之前完全没接触过,所以NOIP之前搞一下.数位dp就是一种dp,emm……用来求解区间[L,R]内满足某个性质的数的个数,且这个性质与数的大小无关. 在这道题中,dp[i][j]代表考虑了i位 ...

  6. 题解 BZOJ1026 & luogu P2657 [SCOI2009]windy数 数位DP

    BZOJ & luogu 看到某大佬AC,本蒟蒻也决定学习一下玄学的数位$dp$ (以上是今年3月写的话(叫我鸽神$qwq$)) 思路:数位$DP$ 提交:2次 题解:(见代码) #inclu ...

  7. 【BZOJ-1026】windy数 数位DP

    1026: [SCOI2009]windy数 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 5230  Solved: 2353[Submit][Sta ...

  8. bzoj 1026 [SCOI2009]windy数 数位dp

    1026: [SCOI2009]windy数 Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline ...

  9. 【bzoj1026】[SCOI2009]windy数 数位dp

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

随机推荐

  1. 使用crontab定时执行脚本时别忘了输出重定向>

    原文:https://blog.csdn.net/solmyr_biti/article/details/50683279 -------------------------------------- ...

  2. outlook 2010 自动密送Email

    以下功能请勿非法使用: 密抄到多人这个需要用到宏 方法一: 1.在Outlook里面键入ALT+F11打开VBA编辑器 2.展开“Project (VbaProject.OTM)/Microsoft ...

  3. ProFTPD配置匿名登录与文件夹訪问权限控制

    对ProFTPDserver配置匿名登录.         查看配置文件proftpd.conf.默认情况下配置文件里的.匿名登录配置User和Group均为ftp. 查看/etc/passwd确认用 ...

  4. python之入门,你好,中国

    print("你好,中国") 可以运行py文件实现一样的效果(py文件编码一定要是utf-8编码) 你好中国基础教程结束!

  5. JMeter 系列之—-02 创建数据库测试计划

    Jmeter创建数据库测试计划,包括如下步骤: 1. 添加数据库jar包 使用不同的数据库,要引入不同的jar包.主要有两种方式: 方式1:直接将jar包复制到jmeter的lib目录 方式2:通过测 ...

  6. Android开发Tips(3)

    欢迎Follow我的GitHub, 关注我的CSDN. 我会介绍关于Android的一些有趣的小知识点. 本文是第三篇, 其余第一篇, 第二篇. imageMogr2/auto-orient/stri ...

  7. linux 输入子系统(1) -Event types

    输入系统协议用类型types和编码codecs来表示输入设备的值并用此来通知用户空间的应用程序. input协议是一个基于状态的协议,只有当相应事件编码对应的参数值发生变化时才会发送该事件.不过,状态 ...

  8. Android实现RecyclerView的下拉刷新和上拉载入很多其它

    需求 先上效果图, Material Design风格的下拉刷新和上拉载入很多其它. 源代码地址(欢迎star) https://github.com/studychen/SeeNewsV2 假设对于 ...

  9. Codeforces Round #422 (Div. 2) A. I'm bored with life 暴力

    A. I'm bored with life     Holidays have finished. Thanks to the help of the hacker Leha, Noora mana ...

  10. bash命令中的两个横

    它是一种标记.命令中的连续的两个横表明选项已经结束了,两个横后面的内容就是参数了,不再是选项了.