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