BZOJ4245 [ONTAK2015]OR-XOR 位運算 貪心
阿新 • • 發佈:2018-12-14
題目連結 題意: 給定一個長度為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段中的斷點,即若且且,那麼相當於從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;
}