其他分享
首页 > 其他分享> > 题解 AT4168 [ARC100C] Or Plus Max

题解 AT4168 [ARC100C] Or Plus Max

作者:互联网

其实这个题目直接枚举子集可以过(复杂度没问题)。

考虑转化一下题目,要对于每个 \(1\leq k\leq 2^n-1\),求 \(\max_{i\operatorname{or}j\leq k,i\neq j}\{a_i+a_j\}\),我们可以先对于每个 \(k\) 求出 \(\max_{i\operatorname{or}j=k,i\neq j}\{a_i+a_j\}\),再求一遍前缀最大值。

思考一下有没有什么特殊性质。发现一个数 \(k\) 的二进制表示的子集里面的所有数都一定 \(\leq k\),所以我们可以求 \(\max_{i\operatorname{or}j\subset k}\{a_i+a_j\}\) 再前缀最大值。

这个问题就转换成了给定一个集合,您需要从中选出两个不同的数,使得他们的和最大。贪心地我们肯定选择最大值和次大值。

那直接枚举 \(k\) 再枚举子集求最大值和次大值就行了。时间复杂度 \(\mathcal{O}(3^n)\),算一下计算量大概是 \(4\times 10^8\),2s 内跑是没问题的。

需要注意一下细节:这里的 \(a_0\) 算所有 \(k\) 的子集(一般写代码的时候不会枚举空集),所以可以先把 \(a_0\) 塞给每个 \(k\),后面忽略掉 \(a_0\) 就行。

还有就是更新最大值和次大值时 \(\geq\) 哪里必须有等号,哪里不需要等号。

代码如下:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
inline int read() {
    int num = 0 ,f = 1; char c = getchar();
    while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
    while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
    return num * f;
}
const int N = 1 << 18;
int maxn[N] ,cmax[N] ,a[N] ,n;
signed main() {
    n = read();
    const int ns = 1 << n;
    for (int i = 0; i < ns; i++)
    	a[i] = read();
    for (int i = 1; i < ns; i++) maxn[i] = max(a[i] ,a[0]) ,cmax[i] = min(a[i] ,a[0]);
    for (int i = 1; i < ns; i++)
    	for (int j = (i - 1) & i; j ; j = (j - 1) & i) { //注意这里 3^n 枚举子集的写法
    		if (a[j] >= maxn[i]) cmax[i] = maxn[i] ,maxn[i] = a[j]; //这里的 >= 不能改成 >,因为可能出现重复的数字,如果改成 > 。
    		else if (a[j] > cmax[i]) cmax[i] = a[j];
		}
	for (int i = 1 ,sum = 0; i < ns; i++) {
		sum = max(sum ,maxn[i] + cmax[i]);
		printf("%d\n" ,sum);
	}
    return 0;
}

标签:cmax,题解,最大值,leq,Plus,Max,maxn,include,sum
来源: https://www.cnblogs.com/Dragon-Skies/p/16243802.html