1. 程式人生 > 其它 >題解 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;
}