2018-9-16【训练日记】

  这两天将数位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;
}

 大概讲解写到了注释中,剩余的理解就得看与这个对应的题目了:

  1,HDU 2089        不要62

不吉利的数字为所有含有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)的数。

扫描二维码关注公众号,回复: 3739081 查看本文章

思路:
    而对于这道题,我们可以用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的个数

其实我觉得,,刷题可以理解一切,就像数学题的公式变换一样,用符号表示记得很熟,换成数字两眼一抹黑,到头来还不如比着例题理解的更快。

猜你喜欢

转载自blog.csdn.net/sodacoco/article/details/82720446
今日推荐