「題解」合成小丹 merge
本文將同步釋出於:
題目
題目描述
資料組數為 \(t\)。
丹最喜歡做的兩件事情分別是踩人和位運算,所以現在他要用位運算來踩你。
丹給了你 \(n\) 個範圍在 \([0,2^\omega)\) 內的非負整數,你需要執行下面兩種操作共 \(n-1\) 次:
- 選擇兩個非負整數 \(x\) 和 \(y\),將兩個非負整數合成成一個非負整數 \(z\),其中 \(z=\left\lfloor\frac{x\mid y}{2}\right\rfloor\)。(這裡的運算子 \(\mid\) 表示的是按位或)
- 選擇一個非負整數 \(x\) 並將其刪去。
不難發現每次操作後你擁有的整數數量都會減少恰好一個,所以在 \(n-1\) 次操作後你會擁有恰好一個非負整數,你需要最小化剩下來的這個整數的值。你只需要輸出最後這個非負整數的值。
\(1\leq t\leq 10\),\(n\leq 10^5\),\(\omega\leq 60\)。
題解
貪心
我們考慮從高位向低位貪心,如果一個位置可以填 \(0\),我們就立刻填好,不難發現這樣得到的答案一定是最優的。
我們的問題就轉化為了如何判定當前位 \(i\) 是否可以選擇 \(0\)。
簡單的判定問題
搜尋題目性質
不難發現,\(\left\lfloor\frac{x\mid y}{2}\right\rfloor=\left\lfloor\frac{x}{2}\right\rfloor\mid\left\lfloor\frac{y}{2}\right\rfloor\)
換句話說,對於兩個數,右移操作和或操作的順序實際上不分先後。
進一步推廣,一個數集經過收縮得到的結果只跟每個元素的右移次數有關,這些數可以根據右移的操作順序排成一棵樹,原先的數都在葉子節點上。
模擬收縮
我們不難貪心地發現,如果一個數可以經過 \(k_0,k_1,k_2,\cdots\) 次操作得到一個合法的,第 \(i\) 位沒有 \(1\) 的形式,我們必然選取其中的最小值 \(k_0\) 作為它右移的次數,否則更難滿足操作的合法性。
我們求出了每個數右移所需位數,直接模擬樹的收縮過程。
每次我們將對應的數兩兩配對,多餘的直接用刪除操作去除,事實上就是每次除 \(2\)。
之後判定是否達到了目的,即右移次數為 \(0\)
快速求解右移次數
如果我們暴力列舉右移的次數,複雜度為 \(\Theta(n\omega^2)\),顯然過不掉。因此我們考慮優化這個部分,不難發現,如果我們知道了上次的限制,我們只需要 \(\Theta(1)\) 的時間即可知道新情況下需要右移多少位。
我們將 \(a_j\) 後刪除 \(i\) 以下的位置,那麼我們就需要將從後往前第一個 \(0\) 挪到 \(i\) 的對應位置。我們將所有的限制或起來,就對應著滿足所有條件的位置。
考慮到 \(0\) 的位置不太好求,我們變換 \(0\) 為 \(1\),將限制的或改成與,則可以用 \(\operatorname{lowbit}\) 解決。
新的解法的時間複雜度為 \(\Theta(n\omega)\)。
參考程式
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;
bool st;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
static char buf[1<<21],*p1=buf,*p2=buf;
inline uint32_t read_uint32(void){
reg char ch=getchar();
reg uint32_t res=0;
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) res=10*res+(ch^'0'),ch=getchar();
return res;
}
inline uint64_t read_uint64(void){
reg char ch=getchar();
reg uint64_t res=0;
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) res=10*res+(ch^'0'),ch=getchar();
return res;
}
const int MAXN=5e5;
const int MAXW=60;
uint32_t n,w;
uint64_t a[MAXN];
uint64_t tag[MAXN];
bool ed;
int main(void){
reg uint32_t t=read_uint32();
while(t--){
n=read_uint32(),w=read_uint32();
for(reg uint32_t i=0;i<n;++i)
a[i]=read_uint64(),tag[i]=(1ull<<(w+1))-1;
reg uint64_t ans=(1ull<<w)-1;
for(reg int i=w-1;i>=0;--i){
static uint32_t cnt[MAXW+1];
static uint64_t tmp[MAXN];
for(reg uint32_t j=0;j<n;++j)
tmp[j]=tag[j]&(~(a[j]>>i)),++cnt[__builtin_ffsll(tmp[j])-1];
reg uint32_t lim=0;
for(reg int j=w;j>=0;--j)
lim=(lim>>1)+cnt[j],cnt[j]=0;
if(lim){
ans^=1ull<<i;
for(reg uint32_t j=0;j<n;++j)
tag[j]=tmp[j];
}
}
printf("%lu\n",ans);
}
fprintf(stderr,"%.3lf s\n",1.0*clock()/CLOCKS_PER_SEC);
fprintf(stderr,"%.3lf MiB\n",(&ed-&st)/1048576.0);
return 0;
}