转载 :https://www.luogu.com.cn/blog/zhoushuyu/solution-p1043
题目链接
题目思路
总结一下这种类似DP题目的思路和技巧吧。
1、破环成链。没有太多的技巧性,具体而言就是把数据存储两遍,使得环形的数据可以链式展开,便于我们去DP。
但最后一定要记得扫一遍答案,取\(F[i][i+N-1]\; i:1->N\)中的最大/小值。
2、前缀和。这个东西并不是在所有情况下都适用,但使用起来真的很方便,可以把O(n)的复杂度优化为O(1)。不过只适用于需要把数据直接相加的地方,比如说这道题。
3、初始化。这里实际上包括两点,一方面是在某些特殊情况下需要初始化,初始化为某特定值(比如本题只分成1段的时候)。另一方面也就是数组初始化,求最大值的时候根本不用管(因为初始默认为0),在求最小值的时候把数组全部赋初值为极大值就好啦。
4、状态表达。一般来说可以用F[i][j]表示在区间[i,j]中怎么怎么样,但由于本题还加了一个分为几段的状态,就把数组直接加一维就好了。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fi first
#define se second
const int maxn=1e2+5,inf=0x3f3f3f3f;
int n,m;
int a[maxn];
int dpma[maxn][maxn][10+5];
int dpmi[maxn][maxn][10+5];
int mod(int a){
return (a%10+10)%10;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){//破环成链
scanf("%d",&a[i]);
a[i+n]=a[i];
}
for(int i=1;i<=2*n;i++){ //前缀和
a[i]+=a[i-1];
}
memset(dpmi,0x3f,sizeof(dpmi));
for(int i=1;i<=m;i++){
for(int j=1;j<=2*n;j++){
dpmi[j][j][i]=dpma[j][j][i]=mod(a[j]-a[j-1]);
}
}
for(int i=1;i<=m;i++){
for(int r=1;r<=2*n;r++){// 右端点
for(int l=r;l>=1;l--){// 左端点
if(i==1){
dpma[l][r][i]=dpmi[l][r][i]=mod(a[r]-a[l-1]);
continue;
}
for(int mid=l;mid<r;mid++){ //中间端点
dpma[l][r][i]=max(dpma[l][r][i],dpma[l][mid][i-1]*mod(a[r]-a[mid])); //[mid+1,r]
dpmi[l][r][i]=min(dpmi[l][r][i],dpmi[l][mid][i-1]*mod(a[r]-a[mid]));
}
}
}
}
int ans1=inf,ans2=0;
for(int i=1;i<=n;i++){
ans1=min(ans1,dpmi[i][i+n-1][m]);
ans2=max(ans2,dpma[i][i+n-1][m]);
}
printf("%d\n%d\n",ans1,ans2);
return 0;
}