区间dp - 洛谷 P1043 - 数字游戏
https://www.luogu.com.cn/problem/P1043
首先,如果学会了这篇题解的思路,那么你应该还可以做对这道题目。
好了,进入正题。
对于题目中的环形,很容易想到破环成链进行处理(当然,对于初次接触'环形'应该比较难想,这里说容易想是建立在接触过类似的题目(如'能量项链'等)前提上。)破环成链的通法是开一个两倍长的数组然后枚举起点,这题这么写同样没错,我一开始也是这么写的。但是我觉得如果这样写,代码太复杂了,好像要套四层循环,比较难看。我们注意到,本题的链长度很小( 1 <= n <= 50),于是我就开了一个二维数组,相当于把每一条链都分开存。我想这么写应该更好理解。
此题破环成链后,接下来要处理的是,如何对于一条链求最优解?只要我们知道了求一条链的最优解的方法,那么本题的答案无非就是对每一条链的最优解再求最优解。
因此,我们定义一个函数用来对每一条链进行dp,然后将每条链的dp结果求最优解。
这个dp函数怎么写呢?显然要用到区间动归。我们这样定义dp数组:
dp[ Maxsize ][ Maxsize ] dp[ i ][ j ] 表示从 1 号元素到 i 号元素, 选取 j 个分界点划分成 ( j + 1)段的最优解。
状态转移方程 : dp[ i ][ j ] = max( dp[ i ][ j ], dp[ k ][ j-1 ] * val( k+1, i) 。 其中, k 为一个循环枚举的变量,它的取值范围为 [ j, i ) , 它表示分成上一个分界点的标号。 val( k+1,i) 是一个我们自己定义的函数,用来求数列的 k+1 号元素 到 i 号元素的累加和模10。
代码:
1 #include <iostream> 2 #define INF 0x3fffffff 3 #define MAX(a,b) (a>b?a:b) // 比STL自带的快很多! 4 #define MIN(a,b) (a<b?a:b) 5 #define Maxsize 50+5 6 int arr[Maxsize][Maxsize]; 7 using namespace std; 8 int val(int front,int back,int arr[]){ 9 int ans = 0; 10 for (int i = front; i <= back; i++) { 11 ans += arr[i]; 12 } 13 ans = ans % 10; 14 return (ans + 10) % 10; 15 } 16 void fun(int arr[],const int n,const int m,int& max_ans,int& min_ans){ 17 int dp[Maxsize][Maxsize]; // 前 i 个数,分成 j 段的答案 18 for (int i = 0; i < Maxsize; i++) { 19 for (int j = 0; j < Maxsize; j++) { 20 dp[i][j] = 0; 21 } 22 } 23 for (int i = 1; i <= n; i++) { // 初始化,分成0段 24 dp[i][0] = ((dp[i-1][0] + arr[i]) % 10 + 10) % 10; 25 } 26 for (int i = 1; i <= n; i++) { // 前 i 个数 27 for (int j = 1; j <= m; j++) { // 分出j个分界点,分界点归并到左半段 28 if(i <= j)continue; 29 for (int k = j; k < i; k++) { // 上一个分界点所对应的位置 30 dp[i][j] = MAX(dp[i][j],dp[k][j-1]*val(k+1,i,arr)); 31 } 32 } 33 } 34 max_ans = MAX(max_ans,dp[n][m]); 35 36 for (int i = 0; i < Maxsize; i++) { 37 for (int j = 0; j < Maxsize; j++) { 38 dp[i][j] = INF; 39 } 40 } 41 for (int i = 1; i <= n; i++) { 42 if(i==1) dp[i][0] = (arr[i] % 10 + 10) % 10; 43 else dp[i][0] = ((dp[i-1][0] + arr[i]) % 10 + 10) % 10; 44 } 45 46 for (int i = 1; i <= n; i++) { // 前 i 个数 47 for (int j = 1; j <= m; j++) { // 分出j个分界点,分界点归并到左半段 48 if(i <= j)continue; 49 for (int k = j; k < i; k++) { // 上一个分界点所对应的位置 50 dp[i][j] = MIN(dp[i][j],dp[k][j-1]*val(k+1,i,arr)); 51 } 52 } 53 } 54 min_ans = MIN(min_ans,dp[n][m]); 55 } 56 int main(){ 57 int n,m; 58 cin >> n >> m; // 分成 m 个部分, 那么就找m-1个分界点 59 m--; 60 for (int i = 1; i <= n; i++) { 61 cin >> arr[1][i]; // 以第一个输入的元素为首元素 62 } 63 for (int i = 2; i <= n; i++) { 64 for (int j = 1; j <= n; j++) { 65 arr[i][j] = arr[i-1][j+1]; 66 } 67 arr[i][n] = arr[i-1][1]; 68 } 69 int max_ans = -INF,min_ans = INF; 70 for (int i = 1; i <= n; i++) { 71 fun(arr[i],n,m,max_ans,min_ans); 72 } 73 cout << min_ans << endl << max_ans; 74 return 0; 75 }
---- suffer now and live the rest of your life as a champion ----