偷看了下7k+大牛的數位統計dp寫法,通常的數位dp可以寫成如下形式:
int dfs(int i, int s, bool e) { if (i==-1) return s==target_s; if (!e && ~f[i][s]) return f[i][s]; 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; }
其中:
f為記憶化數組;
i為當前處理串的第i位(權重表示法,也即后面剩下i+1位待填數);
s為之前數字的狀態(如果要求后面的數滿足什么狀態,也可以再記一個目標狀態t之類,for的時候枚舉下t);
e表示之前的數是否是上界的前綴(即后面的數能否任意填)。
for循環枚舉數字時,要注意是否能枚舉0,以及0對於狀態的影響,有的題目前導0和中間的0是等價的,但有的不是,對於后者可以在dfs時再加一個狀態變量z,表示前面是否全部是前導0,也可以看是否是首位,然后外面統計時候枚舉一下位數。It depends.
於是關鍵就在怎么設計狀態。當然做多了之后狀態一眼就可以瞄出來。
注意:
不滿足區間減法性質的話(如hdu 4376),不能用solve(r)-solve(l-1),狀態設計會更加詭異。
下面給幾個例子:
codeforces 55d / spoj JZPEXT(7k+的鬼畜題,卡時限以及代碼長度,所以狀態設計要注意)
//JZPEXT #include<cstdio> #include<cstring> typedef long long ll; int c[2555],n[20],g[2555][10],h[255][10]; ll f[20][50][255],l,r; int gcd(int x,int y){ return y?gcd(y,x%y):x; } ll dfs(int l,int m,int r,bool z){ if(l==-1)return !(r%m); if(!z&&f[l][c[m]][r]!=-1) return f[l][c[m]][r]; ll res=0; int u=z?n[l]:9; for(int d=0;d<=u;++d) res+=dfs(l-1,g[m][d],l?h[r][d]:r*10+d,z&&d==u); return z?res:f[l][c[m]][r]=res; } ll s(ll x){ int l=0; for (;x;x/=10)n[l++]=x%10; return dfs(l-1,1,0,1); } int main(){ memset(f,-1,sizeof f); int i,j,t; for(i=1,r=-1;i<=2520;++i) c[i]=r+=!(2520%i); for(j=0;j<10;++j){ for(i=1;i<=2520;++i) g[i][j]=j?i*j/gcd(i,j):i; for(i=0;i<252;++i) h[i][j]=(i*10+j)%252; } scanf("%d",&t); while(t--){ scanf("%lld%lld",&l,&r); printf("%lld\n",s(r)-s(l-1)); } return 0; }
hdu 4352
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; ll f[20][1<<10][11]; int num[20]; int news[10][1<<10]; ll l, r; int k; int getnews(int x, int s) { for (int i = x; i < 10; ++i) if (s&(1<<i)) return s^(1<<i)|(1<<x); return s|(1<<x); } ll dfs(int i, int s, bool e, bool z) { if (i==-1) return __builtin_popcount(s)==k; if (!e && f[i][s][k]!=-1) return f[i][s][k]; ll res = 0; int u = e?num[i]:9; for (int d = 0; d <= u; ++d) res += dfs(i-1, z&&!d?0:news[d][s], e&&d==u, z&&!d); return e?res:f[i][s][k]=res; } ll solve(ll x) { int len =0; for (; x; x/=10) num[len++] = x%10; return dfs(len-1, 0, 1, 1); } int T; int main() { int i, j; for (i = 0; i < 10; ++i) for (j = 0; j < 1<<10; ++j) news[i][j] = getnews(i, j); memset(f, -1, sizeof(f)); scanf("%d", &T); for (int t = 1; t <= T; ++t) { scanf("%I64d%I64d%d", &l, &r, &k); printf("Case #%d: ", t); printf("%I64d\n", solve(r)-solve(l-1)); } return 0; }
spoj BALNUM
//BALNUM #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; int num[22]; ll f[22][60000]; int T; int news[60000][10]; int exp[10]; bool ok[60000]; ll l, r; ll dfs(int i, int s, bool e) { if (i==-1) return ok[s]; if (!e && ~f[i][s]) return f[i][s]; ll res = 0; int u = e?num[i]:9; for (int d = s?0:1; d <= u; ++d) res += dfs(i-1, news[s][d], e&&d==u); return e?res:f[i][s]=res; } int getnum(ll x, int num[]) { int len = 0; for (; x; x/=10) num[len++] = x%10; return len; } ll solve(ll x) { int len = getnum(x, num); ll res = 0; for (int i = 1; i < len; ++i) res += dfs(i-1, 0, 0); return res+=dfs(len-1, 0, 1); } bool judge(int exp[]) { for (int i = 0; i < 10; ++i) { if (!exp[i]) continue; if (i&1) { if (exp[i]&1) return 0; } else { if (~exp[i]&1) return 0; } } return 1; } int main() { int i, j, k; memset(f, -1, sizeof f); for (int s = 0; s < 59049; ++s) { j = s; for (i = 0; i < 10; ++i, j/=3) exp[i] = j%3; if (s) ok[s] = judge(exp); else ok[s] = 0; for (i = 0; i < 10; ++i) { int t = exp[i]; if (exp[i]&1) exp[i] = 2; else exp[i] = 1; for (j = 9; j >= 0; --j, news[s][i]*=3) news[s][i] += exp[j]; news[s][i] /= 3; exp[i] = t; } } //printf("#%d\n", news[0][3]); scanf("%d", &T); while (T--) { scanf("%lld%lld", &l, &r); printf("%lld\n", solve(r)-solve(l-1)); } return 0; }
其實和翁教主,肖神討論后,想到了更多的鬼畜題,以后再補充吧。