12105 Bigger is Better ( DP )
阿新 • • 發佈:2019-01-06
題意:
給出n個木棒,擺出能被m整除的最大的數
分析:
有一種非常顯而易見的dp方法:
f[i][j]表示用i根火柴拼出的“%m是j”的最大整數
轉移方程:
f[i+c[k]][(j*10+k)%m]=max{f[i][j]+k}
時間複雜度是O(10*nm),看上去好像非常優秀
但是這樣的狀態值是高精度(能拼成的數可能很大),因此實際計算量非常大
(再說有人願意隨手寫一個高精度嗎。。。)
下面就說一個有點難度,但是效率很高的演算法:
f[i][j]表示拼出“%m是j的i位數”所用的最少火柴
轉移方程:
f[i+1][(j*10+k)%m]=min{f[i][j]+c[k]}
那麼我們怎麼根據f陣列確定解呢
首先我們先確定下來最高位(在轉移的時候順便維護一下就好了)
之後我們從高位向低位列舉每一位的數字
舉個簡單的例子:m=7,並且已經確定最高位是3
首先試著讓最高位為9,如果我們能夠擺出9xx這樣的合法數字,那麼ta一定是最大的
是否可以擺出9xx呢?
因為900%7=4,因此後兩位%7的餘數應該是3
如果d[2][3]+c[9]<=n,那麼最高位就可以是9
重複上述過程,直到所有數字都被確定
在計算過程中,我們需要快速算出形如 x000… 的整數%m的答案,這需要我們一開始預處理一下
tip
d<—INF,d[0][0]=0
注意
我們計算出來的f只是輔助構造解的
我們在構造解的時候運用貪心的思想,讓位數儘量多,每一位儘量大
所以只要符合
d[now-1][(limit-c[k]+m)%m]+c[k]<=n
#include<bits/stdc++.h> using namespace std; int n,m; //n個木棒,能被m整除 int cnt[]={6, 2, 5, 5, 4, 5, 6, 3, 7, 6}; int dp[105][3005]; //dp[i][j]表示i位數字,除以m餘數為j最少要的火柴數 int mod[10][105]; //mod[i][j]表示數字i,後面有j為位0,組成的數字模m的值 int main(){ int cs=1; while(~scanf("%d",&n),n){ scanf("%d",&m); printf("Case %d: ",cs++); memset(dp,0x3f,sizeof dp); dp[0][0]=0; for(int i=0;i<=9;i++){ dp[1][i%m]=min(dp[1][i%m],cnt[i]); } for(int i=2;i<=n/2;i++){ //最多n/2位 全1 for(int j=0;j<m;j++){ for(int k=0;k<=9;k++){ dp[i][(j*10+k)%m]=min(dp[i][(j*10+k)%m],dp[i-1][j]+cnt[k]); } } } int ans=0; for(int i=n/2;i>=0;i--){ if(dp[i][0]<=n){ ans=i;break; } } if(ans==0){ printf("-1\n"); continue; } memset(mod,0,sizeof mod); for(int i=0;i<=9;i++){ mod[i][0]=i%m; for(int j=1;j<=100;j++){ mod[i][j]=mod[i][j-1]*10%m; } } int tmp=0; while(ans>0){ for(int i=9;i>=0;i--){ if(dp[ans-1][(m+tmp-mod[i][ans-1])%m]<=n-cnt[i]){ printf("%d",i); n-=cnt[i]; tmp=(m+tmp-mod[i][ans-1])%m; break; } } ans--; } puts(""); } }