P1018 [NOIP2000 提高組] 乘積最大
阿新 • • 發佈:2021-06-12
傳送門
題目描述
今年是國際數學聯盟確定的“ 2000 ――世界數學年”,又恰逢我國著名數學家華羅庚先生誕辰 90 週年。在華羅庚先生的家鄉江蘇金壇,組織了一場別開生面的數學智力競賽的活動,你的一個好朋友 XZ 也有幸得以參加。活動中,主持人給所有參加活動的選手出了這樣一道題目:
設有一個長度為N的數字串,要求選手使用K個乘號將它分成K+1個部分,找出一種分法,使得這K+1個部分的乘積能夠為最大。
同時,為了幫助選手能夠正確理解題意,主持人還舉了如下的一個例子:
有一個數字串:312, 當N=3,K=1時會有以下兩種分法:
這時,符合題目要求的結果是:31 \times 2 = 62
現在,請你幫助你的好朋友 XZ 設計一個程式,求得正確的答案。
輸入格式
程式的輸入共有兩行:
第一行共有2個自然數N,K(6≤N≤40,1≤K≤6)
第二行是一個長度為N的數字串。
輸出格式
結果顯示在螢幕上,相對於輸入,應輸出所求得的最大乘積(一個自然數)。
輸入輸出樣例
輸入 #1輸出 #14 2 1231
62
說明/提示
NOIp2000提高組第二題
思路
每一段區間的答案可以分解成兩個小區間去做,考慮用區間dp(記憶化搜尋)
由於乘積的結合律和交換律(?),一段區間的dp值即為乘積,轉移時可以直接相乘
關於具體地轉移,列舉乘號放在什麼位置(注意下標不要越界),左右各有幾個乘號
關於遞迴邊界,一開始我有點發蒙,但是實際上就是在k(乘號數)==0時直接返回這個數就可以
最後,實際上這題要AC還需要高精,不過我沒寫
程式碼
#include<bits/stdc++.h> using namespace std; #define ll long long const int INF = 0x3f3f3f3f; int n,k; ll dp[45][45][10]; char s[45]; ll calc(int l,int r) { ll cnt=1,ans=0; for(int i=r;i>=l;i--) { ans+=cnt*(s[i]-'0'); cnt*=10; } return ans; } ll solve(int l,int r,int k) { //if(l>r) return 0; if(!k) return dp[l][r][k]=calc(l,r); if(dp[l][r][k]) return dp[l][r][k]; dp[l][r][k]=0; for(int i=l;i<r;i++) for(int j=0;j<k;j++) //左邊j個乘號,右邊k-j-1個 { if(i-l>=j&&r-(i+1)+1-1>=k-j-1) dp[l][r][k]=max(dp[l][r][k],solve(l,i,j)*solve(i+1,r,k-j-1)), dp[l][r][k]=max(dp[l][r][k],solve(l,i,k-j-1)*solve(i+1,r,j)); } // printf("dp[%d][%d][%d]=%lld\n",l,r,k,dp[l][r][k]); return dp[l][r][k]; } int main() { scanf("%d%d",&n,&k); scanf("%s",s+1); printf("%lld",solve(1,n,k)); return 0; } //4 2 1231