区间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 }

 

 

 

 

posted @ 2020-03-16 21:01  popozyl  阅读(257)  评论(0编辑  收藏  举报