2022 ICPC网络赛(二) B Non-decreasing Array(DP 状态设计)
2022 ICPC网络赛(二) B Non-decreasing Array
题意:
给出一个长度为n的不降的数组a,每次可以进行1次操作,请问操作1次到n次,每次操作后的最大值是多少。
操作:一次操作中又包含着两个操作,第一个是删去数组中的任意一个数,第二是对数组中的任意一个数赋任意值,但是要保证序列仍然保持不降。
思路:
一次操作中虽然是两个操作,但是可以通过臆想证明发现两个操作都等价于删去一个数,那么意思就是一次操作会删去两个数。根据n的大小100,我们可以想到用一个三维dp数组\(f[l][r][k]\)来表示在区间\([l,r]\)中删去k个数。其中k应该小于等于\(len(l, r) - 2\),因为如果大于的话,也不需要删去那么多就已经可以得到我们的最优解也就是只剩两个边界了。那么我们设置出这样一个状态仍然无法转移,因为他的信息太少了。由于题目的最优解是只剩两边边界,那么我们考虑把这个信息也加入f数组的定义中,也就是说\(f[l][r][k]\)现在应该表示,不删l和r,且删去k个数的最优解。想一下n^3的状态转移,简单想到区间DP的转移方式,但又不完全是区间DP。我们第三层for循环枚举的应该是在区间\([l, r]\)中删去几个数,然后只删去连续的一定是最佳的,所以就得到转移方程。
for(int i = 1; i <= k; i ++)
{
int mid1 = l + i + 1, mid2 = r - i - 1;
f[i][j][k] = max(f[l][r][k], (a[mid1] - a[l]) ^ 2 + f[mid1][r][k - i]);
f[i][j][k] = max(f[l][r][k], (a[r] - a[mid2]) ^ 2 + f[l][mid2][k - i]);
//也就是直接删去了[l + 1, mid1 - 1]或者[mid2 + 1, r - 1]
}
实现:
用记忆化搜索的方式会比较容易写出。
#define int long long
const int N = 105;
int f[N][N][N]; //区间左右边界为l, r,已经删除了k次但不会删去边界
int a[N];
int n;
int calc(int l, int r) {return (a[r] - a[l]) * (a[r] - a[l]);}
int dfs(int l, int r, int k)
{
if(l > r) return 0;
if(f[l][r][k] != -1) return f[l][r][k];
int len = r - l + 1;
if(len <= 2) //l + 1 == r
return f[l][r][k] = calc(l, r);
if(len - 2 <= k)
return f[l][r][k] = calc(l, r);
for(int i = 0; i <= k; i ++) //删去i个元素
{
f[l][r][k] = max(f[l][r][k], calc(l, l + i + 1) + dfs(l + i + 1, r, k - i));
f[l][r][k] = max(f[l][r][k], calc(r - i - 1, r) + dfs(l, r - i - 1, k - i));
}
return f[l][r][k];
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i ++)
cin >> a[i];
memset(f, -1, sizeof f);
for(int i = 1; i <= n; i ++)
cout << dfs(1, n, min(2 * i, n - 2)) << '\n';
return 0;
}