poj3208 Apocalypse Someday[数位DP]
数位中出现至少3个连续的'6'的数字(称魔鬼数),询问满足要求的排名k的数。
经典题型。采用试填法。
递推做法:预处理出$i$位数字中满足要求的数(下记为'魔鬼数')。对每一位都从0到9试一遍,然而卡在了试填时试到6这个数时该怎么办,不太会做。然后才知道可以记录填到目前的上一位已有多少个连续的6,这样可以配合当前的计算出来。
所以就有这个$f[i][0]=9*(f[i-1][0]+f[i-1][1]+f[i-1][2]),f[i][1]=f[i-1][0],f[i][2]=f[i-1][1],f[i][3]=10*f[i-1][3]+f[i-1][2]$
其中0表示开头有0个6,1表示开头有一个6,2同理,3表示当前小于等于i位的魔鬼数的数量(也就相当于把各位数的魔鬼数累积起来)
注意是算入前导0的。比如有004566674,因为试填时他也算一种。而开头不必担心,看代码第1次循环就会发现考虑0的时候恰好把小于i位的魔鬼数数量都减掉了,说不太清楚,还是自己想想吧。
用k以辅助记录当前填过的末尾连续的6数量,用于处理填6时计算,每填一个6就多算一下。k到3时表示我已经诞生连续3个的6了,所以后面所有的填法都合法,这是要再单独特殊处理,看code。
其他就是常规的试填。
说两点细节:
注意边界处理 ,考虑预处理DP值的边界,尤其是f[0],f[1]等;
试填也要考虑边界。比如开始时。结束时(最后一位),这直接和f[0][0]的初始化产生了联系。所以两个边界都要看一下检查正确性。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define dbg(x) cerr<<#x<<" = "<<x<<endl
#define ddbg(x,y) cerr<<#x<<" = "<<x<<" "<<#y<<" = "<<y<<endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
template<typename T>inline char MIN(T&A,T B){return A>B?A=B,:;}
template<typename T>inline char MAX(T&A,T B){return A<B?A=B,:;}
template<typename T>inline T _min(T A,T B){return A<B?A:B;}
template<typename T>inline T _max(T A,T B){return A>B?A:B;}
template<typename T>inline T read(T&x){
x=;int f=;char c;while(!isdigit(c=getchar()))if(c=='-')f=;
while(isdigit(c))x=x*+(c&),c=getchar();return f?x=-x:x;
}
ll f[][],rk,cnt;
int T;
inline void preprocess(){
f[][]=,f[][]=;f[][]=;//←注意边界处理 ,DP和试填都要检验一下
for(register int i=;i<=;++i)f[i][]=*(f[i-][]+f[i-][]+f[i-][]),f[i][]=f[i-][],f[i][]=f[i-][],f[i][]=*f[i-][]+f[i-][];
} int main(){//freopen("test.in","r",stdin);freopen("test.out","w",stdout);
read(T);preprocess();while(T--){
read(rk);int x;for(x=;x<=;++x)if(rk<=f[x][])break;
for(register int i=x,k=;i;--i){
for(register int j=;j<=;++j){
cnt=f[i-][];
if(k==)cnt+=f[i-][]+f[i-][]+f[i-][];
else if(j==)for(register int l=-k;l<;++l)cnt+=f[i-][l];
if(rk<=cnt){
if(k<){if(j==)++k;else k=;}
printf("%d",j);break;
}
else rk-=cnt;
}
}
puts("");
}
return ;
}
然后讲一下记搜的,可以套模板。求第k大的数,可以转化为二分枚举区间[1,n]的右边界n,看[1,n]的满足要求的数,由于其随区间变大而单调增,所以是二分。通过不断调整,可以找到最小n使得区间[1,n]恰有k个满足要求的数。这个因为是套模板,所以很好写。但是二分的话会让复杂度多一个log。。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define dbg(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
typedef long long ll;
template<typename T>inline char MIN(T&A,T B){return A>B?A=B,:;}
template<typename T>inline char MAX(T&A,T B){return A<B?A=B,:;}
template<typename T>inline T _min(T A,T B){return A<B?A:B;}
template<typename T>inline T _max(T A,T B){return A>B?A:B;}
template<typename T>inline T read(T&x){
x=;int f=;char c;while(!isdigit(c=getchar()))if(c=='-')f=;
while(isdigit(c))x=x*+(c&),c=getchar();return f?x=-x:x;
}
int T;
ll L,R,f[][][][],b[],aim;
ll dp(int len,bool las,bool las2,bool fil,bool limit){
if(!len)return fil;
if(!limit&&~f[len][las][las2][fil])return f[len][las][las2][fil];
ll cnt=;int num=limit?b[len]:;
for(register int i=;i<=num;++i)cnt+=dp(len-,i==,las,fil||(las&&las2&&i==),limit&&i==num);
return limit?cnt:f[len][las][las2][fil]=cnt;
}
inline ll solve(ll x){
int k=;while(x)b[++k]=x%,x/=;
return dp(k,,,,);
} int main(){//freopen("tmp.txt","r",stdin);//freopen("test.out","w",stdout)£»
memset(f,-,sizeof f);
read(T);while(T--){
read(aim);L=,R=1e10;ll mid;
while(L<R){
mid=L+R>>;
if(solve(mid)<aim)L=mid+;
else R=mid;
}
printf("%lld\n",L);
}
return ;
}
poj3208 Apocalypse Someday[数位DP]的更多相关文章
- poj3208 Apocalypse Someday 数位dp+二分 求第K(K <= 5*107)个有连续3个6的数。
/** 题目:poj3208 Apocalypse Someday 链接:http://poj.org/problem?id=3208 题意:求第K(K <= 5*107)个有连续3个6的数. ...
- POJ 3689 Apocalypse Someday [数位DP]
Apocalypse Someday Time Limit: 1000MS Memory Limit: 131072K Total Submissions: 1807 Accepted: 87 ...
- POJ3208 Apocalypse Someday(二分 数位DP)
数位DP加二分 //数位dp,dfs记忆化搜索 #include<iostream> #include<cstdio> #include<cstring> usin ...
- POJ3208 Apocalypse Someday
题意 Language:Default Apocalypse Someday Time Limit: 1000MS Memory Limit: 131072K Total Submissions: 2 ...
- POJ-3208 Apocalypse Someday (数位DP)
只要某数字的十进制表示中有三个6相邻,则该数字为魔鬼数,求第X小的魔鬼数\(X\le 5e7\) 这一类题目可以先用DP进行预处理,再基于拼凑思想,用"试填法"求出最终的答案 \( ...
- POJ 3208-Apocalypse Someday(数位dp)
题意:给定n,输出第n大包含666的数字. 分析:dp[i][j][k][l]表示 长度为i,当前位是否是6,前一位是否6,是否已经包含666,表示的数量,再用二分找出第n大的这样的数字. #incl ...
- 数位DP专题
这周开始刷数位DP,在网上找到一份神级数位DP模板,做起题目来爽歪歪. http://www.cnblogs.com/jffifa/archive/2012/08/17/2644847.html in ...
- 开坑数位dp
[背景] 在10月3日的dp专练中,压轴的第6题是一道数位dp,于是各种懵逼. 为了填上这个留存已久的坑,蒟蒻chty只能开坑数位dp了. [例题一][HDU2089]不要62 题目大意:给你一个区间 ...
- 数位dp真·浅谈 By cellur925
预警:由于是从$Vergil$学长那里和$Mathison$大神那里学来的,所以清一色记忆化搜索!qwq 巨佬的数位dp讲解(未来的咕咕日报头条): https://www.luogu.org/blo ...
随机推荐
- KVM虚拟迁移(5)
一.迁移简介 迁移: 系统的迁移是指把源主机上的操作系统和应用程序移动到目的主机,并且能够在目的主机上正常运行.在没有虚拟机的时代,物理机之间的迁移依靠的是系统备份和恢复技术.在源主机上实时 ...
- 【LeetCode】打家劫舍系列(I、II、III)
打家劫舍(House Robber)是LeetCode上比较典型的一个题目,涉及三道题,主要解题思想是动态规划,将三道题依次记录如下: (一)打家劫舍 题目等级:198.House Robber( ...
- python基础之元祖tuple
元祖是只读列表,不可哈希,可循环查询,可切片*儿子不能改,孙子可更改--元祖里面单个元素不能更改---元祖内列表可更改增:tu1+tu2查:tu1[index] tu1[start_index:end ...
- 【Linux开发】linux设备驱动归纳总结(四):2.进程调度的相关概念
linux设备驱动归纳总结(四):2.进程调度的相关概念 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- NoSQL--leveldb
什么是leveldb: leveldb它是一个 NOSQL 存储引擎,它和 Redis 不是一个概念.Redis 是一个完备的数据库,而 LevelDB 它只是一个引擎. LevelDB 还可以将它看 ...
- docker-compose 部署elk+解决时间不对导致kibana找不到logstash定义的index + docker-compose安装
1.拉代码 git clone https://github.com/deviantony/docker-elk.git 2.docker-compose配置文件 [root@host7 docker ...
- OKR工作法 目标明确的写下来 - 结果记录- 校准
1.o - objective - 旅程的目的地 - 方向 - 定性的 2.kr - key result - 旅途的下一跳和关键节点 - 定量的 - 需要停下来校准 ################ ...
- 记录几个常用的Css样式效果
1.更改字体,图标大小小于12px无效的问题 若我们设置font-size:10px是不会有效果的,需要使用 transform: scale(0.68); 更改字体最小大小 2.设置div边框虚化, ...
- (转)使用 HTML5 WebSocket 构建实时 Web 应用
HTML5 WebSocket 简介和实战演练 本文主要介绍了 HTML5 WebSocket 的原理以及它给实时 Web 开发带来的革命性的创新,并通过一个 WebSocket 服务器和客户端的案例 ...
- 谷歌云SSH开启root密码登陆
废话不多说,开始教程 1.先选择从浏览器打开ssh连接服务器连接登录成功后,输入以下命令 sudo -i #切换到root passwd #修改密码 然后会要求输入新密码,然后再重复一次密码,输入密码 ...