算法分析
线性dp + 贪心 ?
我们若想要每个子数组求和后按与运算的值尽可能的大,按照贪心的思路,我们就从高位上选,若能满足选中这些数位后,还能划分出 k 个字段则说明这个数字可以被选中
那么现在需要解决的问题就是如何判断划分 k 个子段的合法性,假设当前需要验证 x 的合法性
如果一段区间的和 & ,那么等于就是我们将这个区间加入答案区间中可以贡献出更大的 x ,那么当前的 x 是可以被满足的
那么需要一个 dp 转移一下方案,判断是否可以凑出 >= k 个子段即可
状态表示
表示前 i 个数中是否可以划分出 cnt 个子段
状态转移
f[0][0] = 1;
for(int i = 1;i <= n;i ++)//枚举前 n 个数
for(int j = 0;j < i;j ++)//枚举子区间
if(((s[i] - s[j]) & x) >= x)//判断是否满足条件
for(int cnt = 1;cnt <= k;cnt ++)//枚举所有之前可能产生的子段数量
f[i][cnt] |= f[j][cnt - 1];// 或运算 0 | 1 = 1
若 这段区间满足条件,那么就可以从前 个数划分出 个子段的方案中转移过来
AC Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 110;
int a[N],s[N];
int f[N][N];
int n,k;
bool check(int x)
{
for(int i = 1;i <= n;i ++)
for(int j = 1;j <= n;j ++)
f[i][j] = 0;
f[0][0] = 1;
for(int i = 1;i <= n;i ++)
for(int j = 0;j < i;j ++)
if(((s[i] - s[j]) & x) >= x)
for(int cnt = 1;cnt <= k;cnt ++)
f[i][cnt] |= f[j][cnt - 1];
return f[n][k];
}
signed main()
{
cin >> n >> k;
for(int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
for(int i = 1;i <= n;i ++) s[i] = s[i - 1] + a[i];
// dp[i][j] 表示前 i 个数中是否可以划分出 j 段子数组
// 按照高位去贪心 每次 check 这一位是否可以被选择
// check(int x) check 选取这一位以后的这个数字 是否还能满足 划分出 k 个子段
int ans = 0;
for(int i = 52;i >= 0;i --)
{
ans = ans | (1ll << i);//验证此时的数是否满足
if(!check(ans)) ans = ans ^ (1ll << i);//不能满足 就把他消掉 1 ^ 1 = 0
}
printf("%lld\n",ans);
return 0;
}