蓝桥杯2024年第十五届决赛真题-数位翻转
https://www.dotcpp.com/oj/problem3293.html?sid=16920906&lang=1#editor
小明创造了一个函数\(f(x)\)用来翻转\(x\)的二进制的数位(无前导 0)。比如\(f(11) = 13\),因为$11 = (1011)_2 $ ,将其左右翻转后,变为\(13 = (1101)_2\);再比如\(f(3) = 3\),\(f(0) = 0\),\(f(2) = f(4) = f(8) = 1\)等等。
小明随机出了一个长度为 n 的整数数组\(\{a_1, a_2, ..., a_n\}\),他想知道,在这个数组中选择最多 m 个不相交的区间,将这些区间内的数进行二进制数位翻转(将\(a_i\)变为\(f(a_i)\))后,整个数组的和最大是多少?
输入格式
输入共 2 行。
第一行为两个正整数 n, m。
第二行为 n 个由空格分开的整数 a1, a2, ..., an。
输出格式
输出共 1 行,一个整数表示答案。
样例输入
5 3
11 12 13 14 15
样例输出
67
提示
【样例说明 1】只翻转 a1,和为 13 + 12 + 13 + 14 + 15 = 67。
样例输入
6 2
23 8 11 19 16 35
样例输出
134
翻转区间 [a3, a4] 和 [a6],和为 23 + 8 + 13 + 25 + 16 + 49 = 134。
【评测用例规模与约定】
对于 20% 的评测用例,保证 n, m ≤ 20。
对于 100% 的评测用例,保证 n, m ≤ 1000,\(0 ≤ a_i ≤ 10^9\)。
这个题是个动态规划问题,设置 dp 数组\(dp[i][j][0/1]\)。其中\(dp[i][j][0]\)表示第 \(i\) 位不翻转时,恰好前 \(i\) 位共有 \(j\) 位翻转的最优情况;响应的\(dp[i][j][1]\)表示翻转。
状态转移方程就是:
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e3+100;
ll a[maxn];
ll b[maxn];
ll tmp[100000];
ll dp[maxn][maxn][2];//1是这个变化,0是不变化
ll get(ll x){
ll ans=0,p=1,cnt=0;
while(x){
tmp[++cnt]=x%2;
x/=2;
}
for(int i=cnt;i>=1;i--){
ans=ans+p*(tmp[i]);
p=p*2ll;
}
return ans;
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=get(a[i]);
}
for(int i=1;i<=n;i++){
dp[i][0][0]=dp[i-1][0][0]+a[i];
for(int j=1;j<=m;j++){
dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1])+a[i];
dp[i][j][1]=max(dp[i-1][j-1][0]+b[i],dp[i-1][j][1]+b[i]);
}
}
ll ans=0;
for(int j=0;j<=m;j++){
ans=max(ans,max(dp[n][j][0],dp[n][j][1]));
}
cout<<ans<<endl;
}