数位翻转(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;
}
posted @ 2025-01-04 17:17  shen_kong  阅读(5)  评论(0编辑  收藏  举报