題解 AT4168 [ARC100C] Or Plus Max
阿新 • • 發佈:2022-05-07
其實這個題目直接列舉子集可以過(複雜度沒問題)。
考慮轉化一下題目,要對於每個 \(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; }