1. 程式人生 > >BZOJ4245 [ONTAK2015]OR-XOR 位運算 貪心

BZOJ4245 [ONTAK2015]OR-XOR 位運算 貪心

題目連結 題意: 給定一個長度為n的序列a[1],a[2],…,a[n],請將它劃分為m段連續的區間,設第i段的費用c[i]為該段內所有數字的異或和,則總費用為c[1] or c[2] or … or c[m]。請求出總費用的最小值。

題解: 感覺我自己想很不好想啊,可能會往dp上去想的樣子。感覺還是很不錯的一道題。

正解是對每一位考慮,我們還是把數字都拆成二進位制數,從高位到低位考慮,為了讓最後結果儘可能最小,那麼我們儘可能先滿足高位最終or的結果是0。

很顯然如果m個數or之後某一位是0,那麼必須每個數的這一位都是0,所以我們考慮劃分出m段,使得每一段這一位xor之後都是0。根據異或的性質,同一位的兩個1異或之後會變成0,所以不難發現如果若干個數xor之後是0的條件是這些數1的個數是偶數個。

那麼我們對所有數求一個異或字首和,根據上述的異或性質可以得到:任意兩個異或字首和某一位是0的位置都可以成為這m段中的斷點,即若sum[i]&(1<<x)=0sum[i]\&(1<<x)=0sum[j]&(1<<x)=0sum[j]\&(1<<x)=0i<ji<j,那麼相當於從i+1到j是x這一位異或起來是0。

然後對於每一位,如果可以的斷點大於等於m並且n個數的字首異或和這一位是0,那麼就說明對於當前位是可以劃分出m個區間,使得這一位最終對答案不產生貢獻。對於最優的情況,我們要當前可以選的位置在之前的位都與最優情況的位相同。於是對於每一個可以變成的0,我們都標記那些這一位不能變成0的斷點。如果這一位不能變位0我們就把它計入答案。

程式碼:

#include <bits/stdc++.h>
using namespace std;

int n,m,book[500010];
long long ans,a[500010],sum[500010];
int main()
{
	scanf("%d%d",&n,&
m); for(int i=1;i<=n;++i) scanf("%lld",&a[i]); for(int i=1;i<=n;++i) sum[i]=sum[i-1]^a[i]; for(long long i=62;i>=0;--i) { int ji=0; for(int j=1;j<=n;++j) { if(!book[j]&&(sum[j]&(1ll<<i))==0) ++ji; } if(ji>=m&&(sum[n]&(1ll<<i))==0) { for(int j=1;j<=n;++j) { if(sum[j]&(1ll<<i)) book[j]=1; } } else ans|=(1ll<<i); } printf("%lld\n",ans); return 0; }