数位翻转(dp)
给一 n 个数字的数组, 一个翻转操作将一个数按二进制形式翻转再转回十进制. 问最多翻转 m 个连续段, 完成后数组和最大为多少.
先求贡献数组(翻转后能增加多少), 然后问题转化为数组中选 m 个段和最大, 这和最大连续子数组和是不同的(只有一个段).
定义
\(dp[i][j][0] 代表在递推到第 i 个时, 已经分了 j 段, 不选a[i], 最大的和\)
\(dp[i][j][1] 代表在递推到第 i 个时, 已经分了 j 段, 选a[i], 最大的和\)
可知若求出了 \(递推到第 i 个时, 已经分了 j 段最大的和\) , 第 i + 1 个可以选或者不选
不选的话就是
\(dp[i + 1][j][0] = max(dp[i][j][0], dp[i][j][1])\)
选的话就是
\(dp[i + 1][j][1] = max(dp[i][j - 1][0], dp[i][j][1]) + a[i]\)
最后要返回递推到第n个时分(1 ~ m)段最大的那个 ans = max(ans, dp[n][i][0], dp[n][i][1])
#include<bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned int;
using i128 = __int128;
#define TEST
#define TESTS int _; cin >> _; while(_--)
using namespace std;
int n, m;
i64 a[4000];
i64 dp[4000][4000][2];
i64 flip(int n) {
i64 num = 0;
while(n) {
num = num * 2 + (n % 2);
n /= 2;
}
return num;
}
void Main() {
cin >> n >> m;
i64 sum = 0;
for(int i = 0; i < n; ++i) {
i64 num;
cin >> num;
sum += num;
a[i + 1] = flip(num) - num;
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][1]);
dp[i][j][1] = max(dp[i - 1][j][1], dp[i - 1][j - 1][0]) + a[i];
}
}
i64 ans = 0;
for(int i = 1; i <= m; ++i) ans = max({ans, dp[n][i][0], dp[n][i][1]});
cout << ans + sum << endl;
}
int main() {
ios :: sync_with_stdio(false);
cin.tie(0); cout.tie(0);
TEST Main();
return 0;
}