比较简单的一道dp。
可以考虑以 “已经摆放好的花” 作为阶段。
考虑到花的摆放有先后顺序,我们可以将一个控制花盆范围的东西加入状态。
设 $f_{i,j}$ 表示的集合为:将所有 $1 \sim i$ 的花,放入 $1 \sim j$ 的花盆中(不一定放满,但不过放的花盆一定在 $1 \sim j$)的方案组成的集合。
$f_{i,j}$ 记录的是最大值。
我们以第 $i$ 个花的摆放位置将 $f_i,j$ 划分为若干个小子集。
假设第 $i$ 个花放在 $k$ 的位置。
首先,可以确定的是,$k \leq j$。
其次,一定要在前 $1 \sim i-1$ 个花的后面,即 $i \leq k$ 。
那么问题就转化为了,求 $f_{i-1,k-1}$ 了(将前 $i-1$ 个花放在 $1 \sim k-1$ 的花盆中)。
所以得到转移方程:
$$f_{i,j} = \max_{i \leq k \leq j} \{ f_{i-1,k-1} + c_{i,k} \}$$
枚举 $k$,实现状态转移,复杂度 $O(n^2m)$
至于输出方案,要求字典序最小。
我们可以记录 $pre_{i,j}$ 为 $f_{i,j}$ 在状态转移的时候,第 $i$ 个花摆放的位置,若转移的时候多个位置可以转移,则取最靠前的。
那么,我们 $f_{i,j}$ 一定是从 $f_{i-1,pre_{i,j}-1}$ 转移来的
这样我们可以用 $f_{n,m}$ 反推整个路径。
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 100
#define inf 0x3f3f3f3f
using namespace std;
int n,m;
int c[maxn + 10][maxn + 10];
int f[maxn + 10][maxn + 10];
int pre[maxn + 10][maxn + 10];
int ans[maxn + 10]; //最终答案路径
void print(int i,int j) //输出路径,当前反推到了f(i,j)
{
if(i == 0) return ; //f(1,*) 反推到 f(0,*) 时不用反推了
print(i-1,pre[i][j] - 1); //f(i,j) 是从 f(i-1,pre[i][j]) 转移而来的
cout<<pre[i][j]<<' '; //输出f(i,j)的决策
}
int main()
{
cin>>n>>m;
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
cin>>c[i][j];
}
}
memset(f,-inf,sizeof(f));
for(int i = 0;i <= n;i++) f[i][0] = 0;//初始化
for(int i = 0;i <= m;i++) f[0][i] = 0;
for(int i = 1;i <= n;i++)//枚举状态
{
for(int j = i;j <= m;j++)
{
int maxk = 0;//记录决策点,即
for(int k = i;k <= j;k++) //注意k的取值范围
{
if(f[i-1][k-1] + c[i][k] > f[i][j])//如果可以更新f(i,j),这里写大于号的原因是要去最小的决策点
{
f[i][j] = f[i-1][k-1] + c[i][k];//更新f(i,j)
maxk = k;//更新决策点
}
}
pre[i][j] = maxk;//记录决策点
}
}
cout<<f[n][m]<<endl;//先输出最优解
print(n,m);//再反推路径
return 0;
}