[BZOJ4069][Apio2015]巴厘岛的雕塑(二分+DP)

直接DP不可做(因为按位取或不满足最优化原理)。
而求最大/小与/或/异或和,可以考虑利用二分的思想,在二进制意义下从高到低确定答案的每一位。在此题中要求最小或和,因此从高往低考虑到每一位时,这一位能为 0 就为0,否则就为 1
而如何判断能否为0,就可以DP了。设当前考虑到第 x 位(最低位为第0位),当前结果为 ans ans 的第 0 位到第x1位都暂时定为 0 ):
下面定义一个or(a,b,k),表示 a 的第k位到最高位和 b 的第k位到最高位按位或(得出结果的第 0 位到第k1位都为 0 ):

or(a,b,k)=(a2korb2k)×2k

f[i][j] 表示到第 i 个雕塑,分成j段,并且对于任意的 h>x ,如果 ans 的第 h 位为0,那么这 j 段的或和的第h位也必须为 0 ,在这样的限制下,第x位能否为 0 。边界为f[0][0]=true
转移即枚举上一段的开头 k ,如果存在一个k同时满足这两个条件:
1、 f[k][j1]=true ,即如果到第 k 个雕塑,分成j1段时第 x 位能为0,到第 i 个雕塑分成j段时第 x 位才有可能为0
2、设 sum[] 为雕塑年龄的前缀和,条件为 sum[i]sum[k] 的第 x 位为0
3、对于任意的 h>x ,如果 ans 的第 h 位为0,那么 sum[i]sum[k] 的第 h 位也必须为0,也就是:
or(sum[i]sum[k],ans,x)=ans
满足这 3 个条件则f[i][j]=true
如果最后存在一个 AiB 使得 f[n][i]=true ,则第 x 为定为0
时间复杂度为 O(n3logni=1Yi) ,无法通过 2000 的数据点。
但是 2000 的数据点的特性是 A=1 ,因此设 g[i] 表示到了第 i 位,满足条件并且第x位为 0 ,最少需要分成几段,g[0]=0
这时候转移条件和 f 基本相似,即枚举上一段的开头j,如果这个 j 满足条件,则g[i]=min(g[i],g[j]+1)
最后如果 g[n]B ,那么第 x 位定为1
因此 A=1 时复杂度 O(n2logni=1Yi)
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 2005, INF = 0x3f3f3f3f;
int n, A, B, a[N], g[N]; bool f[N][N];
ll sum[N], ans;
ll Or(ll x, ll y, int k) {
    x >>= k; y >>= k; return (x | y) << k;
}
bool _(int x) {
    int i, j, k; for (i = 0; i <= n; i++) for (j = 0; j <= n; j++)
        f[i][j] = 0; f[0][0] = 1;
    for (j = 1; j <= B; j++) for (i = 1; i <= n; i++)
    for (k = j - 1; k < i; k++) if (f[k][j - 1] &&
        Or(sum[i] - sum[k], ans, x) == ans)
            f[i][j] = 1;
    for (i = A; i <= B; i++) if (f[n][i]) return 1;
    return 0;
}
bool __(int x) {
    int i, j; memset(g, INF, sizeof(g)); g[0] = 0;
    for (i = 1; i <= n; i++) for (j = 0; j < i; j++)
        if (Or(sum[i] - sum[j], ans, x) == ans)
            g[i] = min(g[i], g[j] + 1);
    return g[n] <= B;
}
bool Try(int x) {
    if (A == 1) return __(x);
    return _(x);
}
int main() {
    int i; n = read(); A = read(); B = read();
    for (i = 1; i <= n; i++) sum[i] = sum[i - 1] + (a[i] = read());
    for (i = 43; i >= 0; i--) if (!Try(i)) ans |= 1ll << i;
    cout << ans << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值