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; }