这两天将数位dp 专题看完了,大部分讲解及代码还是有深搜的感觉在里面,不过也碰到不少用二维数组存储,使用递推来实现的,总感觉自己不敲一遍就还是什么也不会,就将有题目链接的题都敲了一遍,感觉还是比较深的。
他们对于深搜解决数位问题很执着,这是大神的深搜模板:
int dfs(int i,int s,bool e){ //i为当前处理串的第i位(权重表示法,也即后面剩下i+1位待填数);
//e表示之前的数是否是上界的前缀(即后面的数能否任意填)。
if(i==-1)return s==target_s; //s为之前数字的状态(如果要求后面的数满足什么状态,
// 也可以再记一个目标状态t之类,for的时候枚举下t);
if(!e&&f[i][s]!=-1) return f[i][s]; //f为记忆化数组, 初始为-1;
int res=0;
int u=e?num[i]:9;
for(int d=first?1:0;d<=u;++d)
res+=dfs(i-1,new_s(s,d),e&&d=u);
return e?res:f[i][s]=res;
}
大概讲解写到了注释中,剩余的理解就得看与这个对应的题目了:
不吉利的数字为所有含有4或62的号码。例如:
62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 10005
using namespace std;
#define Memset(x, a) memset(x, a, sizeof(x))
const int INF = 0x3f3f3f3f;
typedef long long LL;
typedef pair<int, int> P;
#define FOR(i, a, b) for(int i = a;i < b; i++)
int n, m;
int f[8][2];
int digit[10];
int cal_len(int x){ //统计数位长度
int sum = 0;
while(x){sum++; x /= 10;}
return sum;
}
void cal_digit(int x, int len){ //将数字x按位分开存至数组digit[10]中;
Memset(digit, 0); //将数字x按位分开存至数组digit[10]中;
FOR(i, 1, len+1){
digit[i] = x % 10;
x /= 10;
}
}
int dfs(int len, int s, int e){
if(len == 0) return 1;
if(!e && f[len][s] != -1) return f[len][s];
int ans = 0;
int u = e ? digit[len] : 9;
for(int d = 0;d <= u; d++){
if(d == 4 || (s && d == 2)) continue;
ans += dfs(len-1, d == 6, e && d == u);
}
return e ? ans : f[len][s] = ans;
}
int solve(int x){
int len = cal_len(x);
cal_digit(x, len);
int res = dfs(len, 0, 1);
return res;
}
int main() {
//freopen("in.cpp", "r", stdin);
cin.tie(0);
ios::sync_with_stdio(false);
Memset(f, -1);
while(cin >> n >> m){
if(!n && !m) break;
cout << solve(m) - solve(n-1) << endl;
}
return 0;
}
PS:学到了个小东西:
cin.tie与sync_with_stdio加速输入输出
2,hdu3555 Bomb
统计含有‘49’子串数字的个数
#define Memset(x, a) memset(x, a, sizeof(x))
const int INF = 0x3f3f3f3f;
typedef long long LL;
typedef pair<int, int> P;
#define FOR(i, a, b) for(int i = a;i < b; i++)
LL n;
LL f[31][2][2];
int digit[35];
LL z[35];
LL dfs(int len,int have, int s, int e){
if(len == -1) return have;
if(!e && f[len][have][s] != -1) return f[len][have][s];
LL ans = 0;
int u = e ? digit[len] : 9;
for(int d = 0;d <= u; d++){
if(s && d == 9){
ans += dfs(len-1, 1, d == 4, e && d == u);
}else{
ans += dfs(len-1,have, d == 4 , e && d == u);
}
}
return e ? ans : f[len][have][s] = ans;
}
LL solve(LL x){
Memset(digit, 0);
int len = 0;
while(x){
digit[len++] = x%10;
x /= 10;
}
return dfs(len-1, 0, 0, 1);
}
int main() {
//freopen("in.cpp", "r", stdin);
cin.tie(0);
ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--){
cin >> n;
Memset(f, -1);
cout << solve(n) << endl;
}
return 0;
}
3,HDU4734 F(x)
For a decimal number x with n digits (A nA n-1A n-2 ... A 2A 1), we define its weight as F(x) = An * 2n-1 + An-1 * 2n-2+ ... + A2 * 2 + A1 * 1. Now you are given two numbers A and B, please calculate how many numbers are there between 0 and B, inclusive, whose weight is no more than F(A).
题意:
定义十进制数x的权值为f(x) = a(n)*2^(n-1)+a(n-1)*2(n-2)+...a(2)*2+a(1)*1,a(i)表示十进制数x中第i位的数字。
题目给出a,b,求出0~b有多少个权值不大于f(a)的数。
思路:
而对于这道题,我们可以用dp[len][s]表示长度为len且权值不大于s的数。
这道题用记忆化搜索,除边界条件外记录dp[len][s]的值,下一次发现以前已经计算过了就可以直接return;
初值:dp[len][s] = -1;
dfs(len, s, e)表示求长度为len,不超过pre的所有符合条件的值。其中e是用来控制边界的。
dfs过程中当深搜的边界,发现len < 0,s >=0 的时候就返回1.
代码:
#define Memset(x, a) memset(x, a, sizeof(x))
const int INF = 0x3f3f3f3f;
typedef long long LL;
typedef pair<int, int> P;
#define FOR(i, a, b) for(int i = a;i < b; i++)
#define MAX_N 11
int t;
int a, b;
int f[MAX_N][200000];
int digit[MAX_N];
int dfs(int len, int s, int e){
if(len < 0) return s >= 0;
if(s < 0) return 0;
if(!e && f[len][s] != -1) return f[len][s];
int ans = 0;
int u = e ? digit[len] : 9;
for(int d = 0;d <= u; d++){
ans += dfs(len-1, s - d*(1<<len), e && d == u);
}
return e ? ans : f[len][s] = ans;
}
int _f(int x){
int sum = 0;
int k = 1;
while(x){
sum += ((x % 10) * k);
x /= 10;
k *= 2;
}
return sum;
}
int solve(){
int len = 0;
Memset(digit, 0);
while(b){
digit[len++] = b % 10;
b /= 10;
}
//Debug(_f(a));
return dfs(len-1, _f(a), 1);
}
int main() {
//freopen("in.cpp", "r", stdin);
cin.tie(0);
ios::sync_with_stdio(false);
cin >> t;
int cnt = 1;
Memset(f, -1);
while(t--){
cin >> a >> b;
cout << "Case" << " #" << cnt++ << ": ";
cout << solve() << endl;
}
return 0;
}
接下的有点震撼:
https://blog.csdn.net/brodrinkwater/article/details/77587239
全是题目+代码;
包括4和69,以及含有数49的数目,也是用的深搜;还有一个用到了hash映射的深搜题目,CF Beautiful Numbers:这个数能整除它的所有位上非零整数。问[l,r]之间的Beautiful Numbers的个数(没怎么看明白);还有一个比较新奇的,从前往后搜索的一个:可以用vector的reverse的功能,从前往后搜索,加上&的传地址功能,代码显得很简练,我们设dp[i][j][k][0/1]dp[i][j][k][0/1] 表示当前在第i位,前一位的数字为j,前两位的数字为k,是否达到了上届转移很简单了,对应的题目大意是:问你从1到n有多少数是符合其内部没有回文串。
*************************************************************************
> Author: Drinkwater
> Created Time: 2017/8/26 10:12:32
************************************************************************/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#prag\
ma GCC optimize("O3")
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
#define mem(a, b) memset((a), b, sizeof(a))
template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }
LL read()
{
register LL sum = 0,fg = 0;char c = getchar();
while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }
while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }
return fg ? -sum : sum;
}
const int inf = 1e9;
const int maxn = 100000;
LL dp[50][11][11][2],num;
LL n,m;
vector<int>a;
LL dfs(int pos,int pre,int ppre,int lim,int lead = 0)
{
if(pos == a.size())return 1;
if(dp[pos][pre][ppre][lim]!=-1)return dp[pos][pre][ppre][lim];
int up= lim ? a[pos] : 9;
LL res = 0;
REP(i,lead,up)
if(i != pre && i != ppre)
res += dfs(pos+1,i,pre,lim && i == a[pos]);
return dp[pos][pre][ppre][lim] = res;
}
LL solve(LL x)
{
a.clear();
mem(dp,-1);
while(x) { a.push_back(x%10); x/= 10;}
reverse(a.begin(),a.end());
LL res = 0;
REP(i,0,a.size()-1)res+=dfs(i,10,10,i==0,1);
return res + 1;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("numbers.in", "r", stdin);
freopen("numbers.out", "w", stdout);
#endif
int F = 0;
n = read(),m = read();
if(n)n--;
else F = 1;
cout<<solve(m) - solve(n)+F<<endl;
return 0;
}
膜拜。。
剩余的两篇基本上也是前面出现过的题目,大部分也是用深搜实现的,看到了两篇用递推实现的:
1,hdu 5642 数位dp/ 递推
题意:数一个长度为 n的序列 , 并且序列中不能出现长度大于 3 的连续的相同的字符
ll f[N][4]; //依次表示末尾应经出现连续相等字母的数量
f[1][1]=26;
for(int i=2;i<N;i++){
f[i][1]=(f[i-1][1]+f[i-1][2]+f[i-1][3])*25%mod;
f[i][2]=f[i-1][1];
f[i][3]=f[i-1][2];
}
以及:
2,hdu 3555 含有49的数
题意:
找出2^63范围内含有49的数,注意用long long
dp[k][0]=dp[k-1][0]*9+dp[k-1][1]*8; //dp[][0]表示不包含49并且以非4结尾的个数
dp[k][1]=dp[k-1][0]+dp[k-1][1]; //dp[][1]表示不包含49并且以4结尾的个数
dp[k][2]=dp[k-1][1]+dp[k-1][2]*10; //dp[][2]表示包含49的个数
其实我觉得,,刷题可以理解一切,就像数学题的公式变换一样,用符号表示记得很熟,换成数字两眼一抹黑,到头来还不如比着例题理解的更快。