Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
题目连接:洛谷 P3736
题目大意:给定长度为 的 字符串,每次选取连续长度为 的子串进行合并,每种合并都会得到新的 字符,并且会得到分数。求合并后的最大分数。
题目分析:
读完题就发现这道题是一道区间 (因为每次消去一段后其实就是一个区间问题),对于传统的区间 (比如 能量),通常设立的状态形式如下:
表示把区间 消去(或者其他操作)能够得到的最大得分,但是这道题不能这样设立状态,因为我们每次消去长度为K的区间后还剩下了一个数,也就是将连续 个数变为了 个数,所以我们类似设立状态:
表示区间 消除后最后得到的二进制状态为 时的最大得分。
我们先考虑用区间 的方法进行状态转移,也就是枚举区间的断点,假设断点为 ,我们就枚举 就可以了,但是这里要注意的地方在于,我们每次消除的区间长度固定,也就是 ,那么我们对于 的要求也是有要求。除去枚举断点 ,显然我们还需要枚举一个二进制状态,通过这个二进制状态来进行转移,我们假设此处枚举的二进制状态为 ,那么此处的状态转移方程为:
但是这样的状态转移似乎有点小问题,我们实际上并没有一个对于区间本身消除的得分变化,这个时候我们就将长度正好可以消除的二进制状态 直接向 转移,在这里我们用一个临时数组记录即可,大家可以结合状态转移方程理解一下:
参考代码:
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const ll inf = 1e9;
ll ch[305];
ll n, k, num, ans;
ll c[(1 << 16) + 5], w[(1 << 16) + 5], f[305][305][(1 << 8) + 5];
int main(){
scanf("%lld%lld", &n, &k);
num = (1 << k) - 1;
for (ll i = 1; i <= n; ++i) {
scanf("%lld", &ch[i]);
}
for (ll i = 0; i <= num; ++i) {
scanf("%lld%lld", &c[i], &w[i]);
}
for (ll i = 1; i <= n; ++i) {
for (ll j = 1; j <= n; ++j) {
for (ll l = 0; l <= num; ++l) {
f[i][j][l] = -inf;
}
}
}
for (ll i = n; i >= 1; --i) {
for (ll j = i; j <= n; ++j) {
if (i == j) {
f[i][j][ch[i]] = 0;
continue;
}
ll length = j - i;
while (length >= k) {
length -= k - 1;
}
for (ll mid = j; mid >= i + 1; mid -= k - 1) {
for (ll l = (1 << length) - 1; l >= 0; --l) {
if (f[i][mid - 1][l] == -inf) {
continue;
}
if (f[mid][j][0] != -inf) {
f[i][j][l << 1] = max(f[i][j][l << 1], f[i][mid - 1][l] + f[mid][j][0]);
}
if (f[mid][j][1] != -inf) {
f[i][j][l << 1 | 1] = max(f[i][j][l << 1 | 1], f[i][mid - 1][l] + f[mid][j][1]);
}
}
}
if (length == k - 1) {
ll g[2] = {-inf, -inf};
for (ll l = num; l >= 0; --l) {
if (f[i][j][l] != -inf) {
g[c[l]] = max(g[c[l]], f[i][j][l] + w[l]);
}
}
f[i][j][0] = g[0];
f[i][j][1] = g[1];
}
}
}
for (ll i = 0; i <= num; ++i) {
ans = max(ans, f[1][n][i]);
}
printf("%lld", ans);
return 0;
}
复制代码