洛谷 P5154 数列游戏
Description
Solution
明显的区间dp
根据套路,设计状态:
\(f[i][j]\) 表示,删去区间 \([i, j]\) 之间的数,能得到的最大得分。
但是我们发现,这个并不好转移,我们无法记录是否都能被删除。
所以我们转换一下思路。
设 \(f[i][j]\) 表示,区间 \([i, j]\) 能否被完全删除。
那么我们有两种可能:
-
\(a[i]\) 和 \(a[j]\) 不互质,那么我们可以从 \(f[i + 1][j - 1]\) 转移过来。
-
枚举断点 \(k\),我们先令区间 \([i, k]\) 和 区间 \([k + 1, j]\) 合并,在合并区间 \([i, j]\)
有了判断能否删除的状态之后,我们还要统计答案。
所以设 \(g[i]\) 为处理前 \(i\) 个数之后能得到的最大得分。
那么转移方程就是:\(g[i] = max(g[i], g[j - 1] + sum[i] - sum[j - 1])\)
\(g[i]\) 初值为 \(g[i - 1]\),\(sum[i]\) 为前 \(i\) 个数的分数前缀和。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const ll N = 1010;
ll n;
ll a[N], b[N], f[N][N], g[N];
ll sum[N];
inline ll gcd(ll a, ll b){
return !b ? a : gcd(b, a % b);
}
signed main(){
scanf("%lld", &n);
for(ll i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for(ll i = 1; i <= n; i++){
scanf("%lld", &b[i]);
sum[i] = sum[i - 1] + b[i];
}
for(ll i = 1; i < n; i++)
f[i][i + 1] = (gcd(a[i], a[i + 1]) != 1);//f 数组初值
for(ll len = 3; len <= n; len++)
for(ll i = 1; i + len - 1 <= n; i++){
ll j = i + len - 1;
if(len > 3 && gcd(a[i], a[j]) != 1) f[i][j] |= f[i + 1][j - 1];//转移 1
for(ll k = i + 1; k < j - 1; k++)
f[i][j] |= (f[i][k] & f[k + 1][j]);//转移 2
}
for(ll i = 1; i <= n; i++){
g[i] = g[i - 1];
for(ll j = 1; j <= i; j++)
if(f[j][i])
g[i] = max(g[i], g[j - 1] + sum[i] - sum[j - 1]);
}
printf("%lld\n", g[n]);
return 0;
}