区间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+1, i ) , 它表示分成上一个分界点的标号。  val( k+1,i) 是一个我们自己定义的函数,用来求数列的 k+1 号元素 到 i 号元素的累加和模10。

代码:

#include <iostream>
#define INF 0x3fffffff
#define MAX(a,b) (a>b?a:b)
#define MIN(a,b) (a<b?a:b) // 比 STL自带的快很多!
#define Maxsize 50+5
typedef long long ll; 
ll arr[Maxsize][Maxsize]; // 不确定要不要开 long long,可能不用
using namespace std;
ll val(int front,int back,ll arr[]){
    ll ans = 0;
    for (int i = front; i <= back; i++) {
        ans += arr[i];
    }
    ans = ans % 10;
    return (ans + 10) % 10;
}
void fun(ll arr[],const int n,const int m,ll& max_ans,ll& min_ans){
    ll dp[Maxsize][Maxsize]; // 前 i 个数,分成 j 段的答案
    for (int i = 0; i < Maxsize; i++) {
        for (int j = 0; j < Maxsize; j++) {
            dp[i][j] = 0;
        }
    }
    for (int i = 1; i <= n; i++) { // 初始化,分成0段
        dp[i][0] = ((dp[i-1][0] + arr[i]) % 10 + 10) % 10;
    }
    for (int i = 1; i <= n; i++) { // 前 i 个数
        for (int j = 1; j <= m; j++) { // 分出j个分界点,分界点归并到左半段
            for (int k = j+1; k < i; k++) { // 上一个分界点所对应的位置
                dp[i][j] = MAX(dp[i][j],dp[k][j-1]*val(k+1,i,arr));
            }
        }
    }
    max_ans = MAX(max_ans,dp[n][m]);
    
    for (int i = 0; i < Maxsize; i++) {
        for (int j = 0; j < Maxsize; j++) {
            dp[i][j] = INF;
        }
    }
    for (int i = 1; i <= n; i++) {
        if(i==1) dp[i][0] = (arr[i] % 10 + 10) % 10;
        else dp[i][0] = ((dp[i-1][0] + arr[i]) % 10 + 10) % 10;
    }
    
    for (int i = 1; i <= n; i++) { // 前 i 个数
        for (int j = 1; j <= m; j++) { // 分出j个分界点,分界点归并到左半段
            for (int k = j+1; k < i; k++) { // 上一个分界点所对应的位置
                dp[i][j] = MIN(dp[i][j],dp[k][j-1]*val(k+1,i,arr));
            }
        }
    }
    min_ans = MIN(min_ans,dp[n][m]);
}
int main(){
    int n,m;
    cin >> n >> m;
    m--; // 分成 m 个部分, 那么就找m-1个分界点
    for (int i = 1; i <= n; i++) {
        cin >> arr[1][i]; // 以第一个输入的元素为首元素
    }
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            arr[i][j] = arr[i-1][j+1];
        }
        arr[i][n] = arr[i-1][1];
    }
    ll max_ans = -INF,min_ans = INF;
    for (int i = 1; i <= n; i++) {
        fun(arr[i],n,m,max_ans,min_ans);
    }
    cout << min_ans << endl << max_ans;
    return 0;
}

 

 

猜你喜欢

转载自www.cnblogs.com/popodynasty/p/12506566.html