1. 程式人生 > >2018-9-16【訓練日記】

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;
}

 大概講解寫到了註釋中,剩餘的理解就得看與這個對應的題目了:

不吉利的數字為所有含有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:學到了個小東西:

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;
}

接下的有點震撼:

全是題目+程式碼;

包括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的個數

其實我覺得,,刷題可以理解一切,就像數學題的公式變換一樣,用符號表示記得很熟,換成數字兩眼一抹黑,到頭來還不如比著例題理解的更快。