BZOJ4245 [ONTAK2015]OR-XOR 位运算 贪心

版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/82961923

题目链接
题意:
给定一个长度为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段中的断点,即若 s u m [ i ] & ( 1 < < x ) = 0 sum[i]\&(1<<x)=0 s u m [ j ] & ( 1 < < x ) = 0 sum[j]\&(1<<x)=0 i < j i<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;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/82961923