CF939 D
- 讓你把區間分成 \(k\) 段, 段內用 \(xor\) 連線, 段之間用 \(or\) 連線,問你在結果不大於 \(x\) 的前提下, \(k\) 的最大值
- \(1 \leq n \leq 10^5, 0 \leq x,a_i \leq 2^30\)
- 標籤:正向思維,二進位制操作,按位貪心(從高到低)
- 參考題解1,參考題解2
- 註釋版code
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=r;++i)
using namespace std;
using ll=long long;
const int N=1e5+5;
int t,n,x,ans=-1;
inline void sol(vector<int> a,const int p=30){// now deal with dep p
if(p<0) return ans=max(ans,(int)a.size()),void();
vector<int> pos;//pos: the set of the positione of all element that is 1 in pth bit
//Attention: pos take account for postion, rather than the concrete element
F(i,0,(int)a.size()-1){
if((a[i]>>p)&1) pos.emplace_back(i);
}
//odd
if(pos.size()%2==1 && ((x>>p)&1)==0) return;
//the pth of x is 0 => fail?
if(pos.size()%2==1 && ((x>>p)&1)==1) return sol(a,p-1);
//the pth of x is 1 => continue to solve
//even
bool rmv[(int)a.size()];
vector<int> b,c=a;//copy a to c
memset(rmv,0,sizeof(rmv));
//create the new seq
for(int i=0;i<(int)pos.size();i+=2){//every postion with 1
for(int j=pos[i]+1;j<=pos[i+1];++j){
rmv[j]=1; c[pos[i]]^=c[j];
}
}
F(i,0,(int)a.size()-1){
if(!rmv[i]) b.emplace_back(c[i]);
}
//make decision
if(((x>>p)&1)==0) return sol(b,p-1);//注意是return.兩種情況選其一,單次複雜度就是 O(N)
ans=max(ans,(int)b.size());
//如果x上的這一位是1,第一,若按b這種分法,那麼已經最優了
return sol(a,p-1);
//第二,那麼其實這一位的限制沒有任何意義,仍然踩在a的基礎上,直接去考慮下一位.
}
//核心:倒著做,簡單來說對於每一次操作,奇數個1就直接考慮最終結果,
//偶數個1就按最優策略(相鄰兩兩配對)新增xor,這樣能使這一位上的異或和最終一定為0(儘可能小),剩下沒確定符號的位置在solve(b,p-1)中繼續討論
//發現了嗎,沒有討論or新增的過程,或者換句話說or的新增過程是依靠貪心來約束的.
//b.size()即段數,不一定最優
signed main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin>>t; while(t--){
cin>>n>>x;
vector<int> a(n);
F(i,0,n-1) cin>>a[i];
ans=-1;
sol(a);
cout<<ans<<'\n';
}
return 0;
}
- 反思:
- 經典的二進位制貪心,從低位到高位。核心在以貪心的方式把 \(or\) 運算給迴避掉了,這樣就這剩下一種運算需要處理。
- 另一方面就是直白的正向思維(感覺有點兒反套路化逆向思維的感覺),就逐漸逐漸加符號上去,而不是先全填成 \(or\) 或者 \(xor\) 再去改。想多了的話反而容易遭。
- 二進位制貪心不一定是什麼一個字首相同然後0,1之間討論大小關係, 但共同點是兩種貪心都和勢能很像:只關注已有的條件來推測最終的結果,中間具體狀態不關心。