數位DP問題整理(一)
第一題:Amount of degrees (ural 1057)
題意:[x,y]範圍內的數,可以拆分成k個b進位制的不同冪的和 的數字有多少。
我們可以將x轉換成二進位制來討論。二進位制轉化時,找到第一個非0非1的數,將其及其後面的數都變為1.
那麼問題就變成了求[0,x]範圍內,二進位制表示中含有k個1的數字有多少個。
求[x,y]區間相減。我們可以給數建立0,1的表示樹。
在求高度為i的完全二叉樹中含有j個1的路徑有多少個時,遞推式為:f[i,j] = f[i-1,j-1] + f[i-1,j]
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <map> #include <queue> #include <algorithm> using namespace std; int f[35][35]; int d[35]; //高度為i(i>=0)時,含有j個1的個數 void init() { memset(f,0,sizeof(f)); f[0][0] = 1; for(int i=1;i<=31;i++) { f[i][0] = 1; for(int j=1;j<=i;j++) { f[i][j] = f[i-1][j-1] + f[i-1][j]; } } } //[0,x]範圍內二進位制含有k個1的個數 int calc(int x,int k) { //路徑上含有的1的個數 int tot = 0; int ans = 0; for(int i=31;i>0;i--) { if(x&(1<<i)) { tot++; if(tot>k) break; x ^= (1<<i); } if((1<<(i-1))<=x) ans += f[i-1][k-tot]; } if(tot + x == k) ans++; return ans; } //b進位制轉化為二進位制 int transfer(int b,int x) { int m = 0; int ans = 0; while(x) { d[m++] = x % b; x/=b; } for(int i=m-1;i>=0;i--) { if(d[i]>1) { for(int j=i;j>=0;j--) ans |= (1<<j); } else ans |= d[i]<<i; } return ans; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif int x,y; int k,b; init(); while(scanf(" %d %d",&x,&y)!=EOF) { scanf(" %d %d",&k,&b); x = transfer(b,x-1); y = transfer(b,y); printf("%d\n",calc(y,k) - calc(x,k)); } return 0; }
第二題:windy數。
題意:求給定區間範圍內的,求相鄰數位之差絕對值不小於2的數的個數。
第三題:Hdu 2089 不要62#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <map> #include <queue> #include <algorithm> using namespace std; int A[12]; int f[12][10]; //f[i][j]代表長度為i,最高位為j的windy數個數 void init() { memset(f,0,sizeof(f)); for(int i=0;i<10;i++) f[1][i] = 1; for(int i=2;i<=10;i++) { for(int j=0;j<10;j++) { for(int k=0;k<10;k++) { if(abs(j-k)>1) f[i][j] += f[i-1][k]; } } } } //(0,a)範圍內的windy數個數 int calc(int a) { int m = 0; while(a) { A[m++] = a%10; a/=10; } int ans = 0; //先處理長度小於m的windy數的個數 for(int i=1;i<m;i++) { //題目要求不含前導0 for(int j=1;j<10;j++) { ans += f[i][j]; } } //長度等於m且最高位和原數不同且小於原數的windy數 for(int j=1;j<A[m-1];j++) ans += f[m][j]; //依次迴圈將最高位 變為和原數相同 for(int i=m-1;i>=1;i--) { for(int j=0;j<A[i-1];j++) { if(abs(j-A[i]) > 1) ans += f[i][j]; } if(abs(A[i] - A[i-1])<=1) break; } return ans; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif int a,b; init(); while(scanf(" %d %d",&a,&b)!=EOF) { int ans = calc(b+1) - calc(a); printf("%d\n",ans ); } return 0; }
求給定區間中不含有62和4的數的個數。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <map> #include <queue> #include <algorithm> using namespace std; int dp[10][3]; int A[10]; //(0,a]範圍內有多少個吉利數 int calc(int a) { int sum = a; int m = 0; int ans = 0; bool flag = false; while(a) { A[++m] = a%10; a/=10; } A[m+1] = 0; for(int i=m;i>=1;i--) { ans += dp[i-1][2] * A[i]; if(flag) { ans += dp[i-1][0] * A[i]; } else { if(A[i]>4) ans += dp[i-1][0]; if(A[i+1] == 6 && A[i]>2) ans += dp[i][1]; if(A[i]>6) ans += dp[i-1][1]; if(A[i] == 4 || (A[i+1] == 6 && A[i] == 2)) flag = true; } } //數本身 if(flag) ans++; return sum - ans; } //dp[i][0]:長度<=i的吉利數個數 //dp[i][1]:長度為i,且最高位含有2的吉利數個數 //dp[i][2]:長度<=i的非吉利數個數 void init() { memset(dp,0,sizeof(dp)); dp[0][0] = 1; for(int i=1;i<=8;i++) { dp[i][0] = dp[i-1][0]*9 - dp[i-1][1]; dp[i][1] = dp[i-1][0]; dp[i][2] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] * 10; } } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif int a,b; init(); while(scanf(" %d %d",&a,&b)!=EOF) { if(a == 0 && b == 0) break; int ans = calc(b) - calc(a-1); printf("%d\n",ans); } return 0; }
第四題:Hdu 3555 Bomb
題意:求給定區間的含有49的數的個數。
方法與上題類似,比上題要簡單許多。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <map>
#include <queue>
#include <algorithm>
using namespace std;
#define LL __int64
LL dp[25][3];
int A[25];
//(0,a]範圍內有多少個吉利數
LL calc(LL a)
{
int m = 0;
LL ans = 0;
bool flag = false;
while(a)
{
A[++m] = a%10;
a/=10;
}
A[m+1] = 0;
for(int i=m;i>=1;i--)
{
ans += dp[i-1][2] * A[i];
if(flag)
{
ans += dp[i-1][0] * A[i];
}
else
{
if(A[i]>4) ans += dp[i-1][1];
if(A[i+1] == 4 && A[i] == 9) flag = true;
}
}
//數本身
if(flag) ans++;
return ans;
}
//dp[i][0]:長度<=i的不含49的數的個數
//dp[i][1]:長度為i,且最高位含有9的不含49的數的個數
//dp[i][2]:長度<=i的含有49的數個數
void init()
{
memset(dp,0,sizeof(dp));
dp[0][0] = 1;
for(int i=1;i<=22;i++)
{
dp[i][0] = dp[i-1][0]*10 - dp[i-1][1];
dp[i][1] = dp[i-1][0];
dp[i][2] = dp[i-1][2] * 10 + dp[i-1][1];
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
int t;
LL a;
init();
scanf(" %d",&t);
while(t--)
{
scanf(" %I64d",&a);
LL ans = calc(a);
printf("%I64d\n", ans);
}
return 0;
}
第五題:Hdu 3709 Balanced Number
平衡數。列舉平衡位置。採用記憶化搜尋的方式記錄已有的值。加適當剪枝。然後排除掉重複的0即可。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <map>
#include <queue>
#include <algorithm>
using namespace std;
#define LL long long
#define Maxn 20
LL dp[Maxn][Maxn][2005];
int digit[Maxn];
LL dfs(int pos,int pivot,int pre,bool limit)
{
if(pos<=0) return pre == 0;
if(pre<0) return 0;
if(!limit && dp[pos][pivot][pre]!=-1) return dp[pos][pivot][pre];
int end = limit ? digit[pos] : 9;
LL ans = 0;
for(int i=0;i<=end;i++)
{
ans += dfs(pos-1,pivot,pre + i*(pos-pivot),limit && (i == end));
}
if(!limit) dp[pos][pivot][pre] = ans;
return ans;
}
LL calc(LL a)
{
if(a<0) return 0;
int len = 0;
LL ans = 0;
while(a>0)
{
digit[++len] = a%10;
a/=10;
}
for(int i=1;i<=len;i++)
{
ans += dfs(len,i,0,1);
}
ans = ans - len + 1;
return ans;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
int t;
LL x,y;
scanf(" %d",&t);
memset(dp,-1,sizeof(dp));
while(t--)
{
scanf(" %I64d %I64d",&x,&y);
printf("%I64d\n",calc(y) - calc(x-1) );
}
return 0;
}
第六題:Hoj 1983 Beautiful numbers
題意:如果一個數能夠被其每個數位的數都整除,那麼這個數就叫做美麗數。
基本思路是用:dp[len][mod][lcm]表示<=len的長度中,此數為mod,各數位的最小公倍數為lcm的數的個數來進行記憶化搜尋。方法和上一題類似。
但我們發現,len在[1,20]範圍內,mod在[1,1^18]範圍內,lcm在[1,2520]範圍內。所以dp陣列肯定超記憶體。
下面我們來進行記憶體優化:
假設這個數為a,各個數位的值分別為ai,那麼我們發現lcm(ai) | a.
而[1,9]的最小公倍數是2520.那麼lcm(ai) | 2520, 所以lcm(ai) | (a%2520).
所以第二維大小我們可以從1^18降到2520,方法是%2520.
現在的dp陣列的記憶體是20*2520*2520,還是很大。
然後我們再考慮:
我們發現某一個數的各個數位的數的最小公倍數最大是2520,而且只能是2520的公約數。而2520的公約數有48個。所以第三維我們只用[50]的空間就行了。
方法是用Hash進行離散化。‘
這樣記憶體就成了20*2520*50,可以拿下這道題目了。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <map>
#include <queue>
#include <algorithm>
using namespace std;
#define LL long long
LL dp[20][2525][55];
int digit[20];
int hash[2525];
int gcd(int a,int b)
{
if(b == 0) return a;
return gcd(b,a%b);
}
int calc_lcm(int a,int b)
{
return a/gcd(a,b)*b;
}
LL dfs(int pos,int mod,int lcm,bool limit)
{
LL ans = 0;
if(pos<=0) return mod % lcm == 0;
if(!limit && dp[pos][mod][hash[lcm]]!=-1) return dp[pos][mod][hash[lcm]];
int end = limit ? digit[pos] : 9;
for(int i=0;i<=end;i++)
{
ans += dfs(pos-1,(mod*10+i)%2520,i?calc_lcm(lcm,i):lcm,limit && (i==end));
}
if(!limit) dp[pos][mod][hash[lcm]] = ans;
return ans;
}
LL calc(LL a)
{
if(a<0) return 0;
int len = 0;
while(a>0)
{
digit[++len] = a%10;
a/=10;
}
//0也當作其中的一個美麗數,因為兩者相減會抵消掉
LL ans = dfs(len,0,1,1);
return ans;
}
void init()
{
memset(dp,-1,sizeof(dp));
int id = 0;
for(int i=1;i*i<=2520;i++)
{
if(2520%i == 0)
{
hash[i] = id++;
if(i*i!=2520) hash[2520/i] = id++;
}
}
//printf("id = %d\n", id);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
init();
int t;
LL x,y;
while(scanf(" %lld %lld",&x,&y)!=EOF)
{
printf("%lld\n",calc(y) - calc(x-1));
}
return 0;
}
第七題:吉哥系列故事——恨7不成妻
與上一題做法也類似,只不過dp需要儲存三種值,所以把它結構體了
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <map>
#include <queue>
#include <algorithm>
using namespace std;
#define MOD 1000000007
#define LL __int64
int digit[20];
LL power[20];
struct Node
{
LL n,s,sq;
}dp[20][10][10];
Node dfs(int pos,int mod,int modSum,bool limit)
{
if(pos<=0)
{
Node t;
t.n = (mod!=0 && modSum!=0);
t.s = t.sq = 0;
return t;
}
if(!limit && dp[pos][mod][modSum].n!=-1) return dp[pos][mod][modSum];
int end = limit ? digit[pos] : 9;
Node ans,temp;
ans.n = ans.s = ans.sq = 0;
for(int i=0;i<=end;i++)
{
if(i == 7) continue;
temp = dfs(pos-1,(mod*10+i)%7,(modSum+i)%7,limit && (i == end));
ans.n = (ans.n + temp.n)%MOD;
ans.s = (ans.s + temp.s + ((i * power[pos])%MOD * temp.n) % MOD) % MOD ;
ans.sq = (ans.sq + temp.sq + ((2*i*power[pos])%MOD*temp.s)%MOD + (((i*i*power[pos])%MOD*power[pos])%MOD*temp.n)%MOD)%MOD;
}
if(!limit) dp[pos][mod][modSum] = ans;
return ans;
}
LL calc(LL a)
{
int len = 0;
while(a>0)
{
digit[++len] = a%10;
a/=10;
}
Node ans = dfs(len,0,0,true);
return ans.sq;
}
void init()
{
memset(dp,-1,sizeof(dp));
memset(power,0,sizeof(power));
power[1] = 1;
for(int i=2;i<=19;i++)
{
power[i] = (power[i-1] * 10)%MOD;
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
int t;
LL l,r;
init();
scanf(" %d",&t);
while(t--)
{
scanf(" %I64d %I64d",&l,&r);
LL ans = (calc(r) - calc(l-1) + MOD)%MOD;
printf("%lld\n", ans);
}
return 0;
}