您的位置:首页 > 其它

JZOJ4828. 【GDOI2017模拟10.30】最大值 分类讨论+分治处理子集问题

2016-10-30 20:25 423 查看

题目大意

给定一个包含n个正整数的序列a,以及一个运算操作符op,要求你输出aiopaj(1≤j≤n)结果中的最大值。

运算符op可以是and,or,xor。有t组测试数据。

n≤105

t≤6

ai≤220

解题思路

这题的弱化版是可以用根号算法过的,可是有以这题的ai较大,所以根号算法的复杂度过不去。那么考虑对于三种不同的运算符分开讨论,并且统计每一位的贡献。

1.op=xor:这种情况是三种运算符中最简单的,对于每个ai,二进制的每一位都有最优取值(即选0或选1肯定有一个更优),所以只需把trie树构出来贪心的跑就可以了。

2.op=and:对于and的情况,当ai中的某一位等于0时,对于0和1的贡献是一样的,所以不能直接在trie上跑。转化一下思路,我们只考虑受到限制为二进制位,即等于1的位置,这里是可以贪心选1的。现在问题就转化为了,我们要限制一个二进制数的其中几位是1,当前位能否是1。设fx表示在a序列中存在多少个数二进制中1所在的位置包含x二进制中1的位置。

考虑如何求得f数组,我们可以用分治的思想,把[0,221−1]的数分成[0,220−1]和[220,221−1]讨论,假设我们已经分别处理好了两边的f数组,但还未考虑后面区间对前面区间的贡献,那么我们只需令fx+=fx+220(x∈[0,220−1])即可!因为fx的转移可以按是否包含220这位讨论,结果就是这两个数加起来,对于两个子区间的计算,递归进去就可以了。那么最后我们判断时只需判断fx是否大于1即可(还有ai本身)。

3。op=or:与op=and的情况一样,考虑一下细节即可,在这里不详述。

程序

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 1e5 + 5, MAXM = 1 << 21;

struct Node {
int go[2];
} tr[MAXN * 4];

int n, k, top, a[MAXN], d[25], f[MAXM + 5];

void solve(int l, int r, int bit) {
if (l == r) return;
int mid = (l + r) >> 1;
solve(l, mid, bit - 1), solve(mid + 1, r, bit - 1);
for (int i = mid + 1; i <= r; i ++) f[i - (1 << bit)] += f[i];
}

void prepare() {
memset(f, 0, sizeof f);
for (int i = 1; i <= n; i ++) f[a[i]] ++;
solve(0, MAXM - 1, 20);
}

void div(int x) {
top = 0;
memset(d, 0, sizeof d);
for (; x; x >>= 1) d[top ++] = x % 2;
}

void solve_xor() {
memset(tr, 0, sizeof tr);
int root = 1, tot = 1, ans = 0;
for (int i = 1; i <= n; i ++) {
div(a[i]);
for (int j = 20, now = root; j + 1; j --) {
if (!tr[now].go[d[j]]) tr[now].go[d[j]] = ++ tot;
now = tr[now].go[d[j]];
}
int get = 0;
for (int j = 20, now = root; j + 1; j --) {
if (tr[now].go[d[j] ^ 1]) {
now = tr[now].go[d[j] ^ 1];
get += 1 << j;
} else now = tr[now].go[d[j]];
}
ans = max(get, ans);
}
printf("%d\n", ans);
}

void solve_and() {
int ans = 0;
for (int i = 1; i <= n; i ++) {
div(a[i]);
int get = 0;
for (int j = 20; j + 1; j --) {
if (!d[j]) continue;
if (f[get + (1 << j)] > 1) get += (1 << j);
}
ans = max(ans, get);
}
printf("%d\n", ans);
}

void solve_or() {
int ans = 0;
for (int i = 1; i <= n; i ++) {
div(a[i]);
int get = 0, now = 0;
for (int j = 20; j + 1; j --) {
if (d[j]) {
get += (1 << j);
continue;
}
if (f[now + (1 << j)] > 0) get += (1 << j), now += (1 << j);
}
ans = max(ans, get);
}
printf("%d\n", ans);
}

void solve() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
prepare();
if (k == 1) solve_and();
if (k == 2) solve_xor();
if (k == 3) solve_or();
}

int main() {
int t;
scanf("%d", &t);
for (int i = 1; i <= t; i ++) solve();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: