CF1743 Codeforces Round #832 (Div. 2)

A.Two Groups

签到题,把正负数分开放再相减即可.赛后从大佬们那得知也可以直接加的,最后取个abs.膜拜大佬!

B.BAN BAN

构造.显然每次交换最多可以破坏前面一个BAN,破坏后面一个BAN.(赛时写的是把A通过破坏最后面的B和N,尽量后移,那样的话破坏前面两个BAN时才只破坏了后面一个BAN,不是最优解,最优应该是每次换掉后面的N(或者B,两者不能同时选),用前面的A或者B换都可以.)

C. Swap Game

假设A操作完后,选了后面的一个数 \(k\) ,把 \(k\)移到最前面,那么经过B的一次操作后,会使得 \(k\) 减一,然后又放到后面.那A可以再选 \(k\) ,然后B再把 \(k\)减一再放到后面,直到B把那个数减到0,A把这个数移到最前面,A获胜.那么,A选的数越小,选的那个数到0所需的回合更短,更有可能赢得比赛.
但是如果游戏开始前,第一个数就很小了,比A从后面选的最小的数还要小,那么B就可以一直操纵这个数,让A来把它减到0,这样B能更快地获得胜利.
因此只需要比较第一个数和后面的数的大小关系.如果后面有比第一个数更小的数,A获胜,反之B获胜.

D. Yet Another Problem

参考:https://zhuanlan.zhihu.com/p/580566353
题意:给定一个数组,并有一些询问,对于询问 \(l,r\),你需要回答是否能通过任意次的操作,使得这段区间的下标所有元素值都为\(0\),其中操作为:首先从 \(l,r\) 中 选出一段长度为奇数的区间\(L,R\),然后让这段区间的所有元素值等于这些元素异或的值.
如果能通过任意次的操作使得\(l,r\) 内的所有元素为\(0\),输出最小操作次数,否则输出\(-1\).

题解:
结论1: 如果区间内本身的异或和为不为0,那么答案为-1。

结论2: 如果区间内本身全为0,那么答案为0。

结论3: 如果区间异或和为0且区间长度为奇数,那么答案为1,也就是直接选择整段区间一次完成。

如果区间\([L,R]\)异或和为0且长度为偶数:
①如果区间\([L,R]\)两头中任意一头是0,那么就能把是0的那头去掉,变成结论3中的情况.
②对于区间\([L,R]\),只需要找到长度为奇数的\([L,j]\)中元素异或和为0(区间前\(x\)个数异或和为0),这样只需要操作两次后\([L,R]\)就全部变成0了
③可以预见到,只需要看前缀是否有异或和为0,而不需要考虑后缀异或和为0,因为如果存在后\(x\)个数异或和为0,那么剩下的那部分异或和也一定为0,总区间长度为偶数,所以剩下那部分即满足前缀异或和为0的且长度为奇数.故如果②无解,答案为-1.
怎么快速求出\([L,j]\)的异或和?没有必要一个一个异或,可以预处理出前缀异或和\(s[i]\),那么\([L,j]\)异或和就等于\(s[j]\oplus s[L-1]\)
对于每个给定的区间\([L,R]\),当然可以从\(L\) 开始找前奇数个数异或和为0的数的位置,但是这样会TLE.
朴素版代码:

点击查看代码
#include<stdio.h>
#include<iostream>
#include<cstdlib>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long LL;
LL gcd(LL a,LL b){if(a<b)swap(a,b);return b==0?a:gcd(b,a%b);}
LL lcm(LL a,LL b){return a/gcd(a,b)*b;}
LL qmi(LL n,LL m,LL mod){LL res=1%mod;LL t=n;while(m){if(m&1)res=res*t%mod;n=n*n%mod;m>>=1;}return res;}

const int N=2e5+10;
int a[N];
int s[N];
int sum[N];
int main()
{
	//freopen("uva.txt","r",stdin);
	int n;
	int q=1;
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		s[i]=s[i-1]^a[i];
		sum[i]=sum[i-1]+a[i];
	}	
	while(q--)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		int len=r-l+1;
		if(sum[r]-sum[l-1]==0)//全为0时
		{
			printf("0\n");
			continue;
		}		
		if(len%2)
		{

			if(s[r]^s[l-1])//a[l]~a[r]异或起来不为0
			{
				printf("-1\n");
				continue;
			}
			else printf("1\n");
		}
		else
		{
			if(s[r]^s[l-1])
			{
				printf("-1\n");
				continue;
			}
			if(a[l]==0||a[r]==0)
			{
				printf("1\n");
				continue;
			}
			int idx=0;
			for(int i=l;i<r;i+=2)
			{
				//printf("%d\n",s[i]^s[l-1]);
				if((s[i]^s[l-1])==0)
				{
					idx=i;
					break;
				}
			}		
			if(!idx)
			{
				printf("-1\n");
			}
			else printf("2\n");
		}

	}
	return 0;
}

因此需要想办法优化,快速找到对于区间前 \(x\) 个数异或和为0的解.可以用map<int,vector<int>>存储前缀异或和为 \(key\) 的所有下标.只需计算出前\(L-1\)的前缀异或和,然后找在\([L,R]\)中的下标为 \(j\) 的前缀异或和等于\(L-1\)的位置的下标,这用lower_bound可以很容易实现.
image

因为要保证\([l,j]\)的长度为奇数,不妨建两个map,下标为奇数的存到一个map,下标为偶数的存到另一个map,这样只需要对 \(l\) 的奇偶分类讨论,如果 \(l\)是奇数,就去存奇数下标的map里找,如果是偶数,就去存偶数下标里的map去找,这样就能一直满足长度是奇数的条件了.

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N];
int n;
int sum[N];
int s[N];
int q;
map<int,vector<int> >v1,v2;
void pre()
{
	for(int i=1;i<=n;i++)
	{
		s[i]=s[i-1]^a[i];
		if(i&1)
		{
			v1[s[i]].push_back(i);
		}
		else v2[s[i]].push_back(i);
	}
}

int solve(int l,int r)
{
	if(l&1)
	{
		auto now = lower_bound(v1[s[l-1]].begin(),v1[s[l-1]].end(),l);
		if(now==v1[s[l-1]].end()||*now>r)return -1;
		
		return *now;
	}
	else
	{
		auto now = lower_bound(v2[s[l-1]].begin(),v2[s[l-1]].end(),l);
		if(now==v2[s[l-1]].end()||*now>r)return -1;
		
		return *now;		
	}
	
	return -1;
}

int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
	pre();
	while(q--)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		if(sum[r]-sum[l-1]==0)//区间内的数全为0
		{
			printf("0\n");
			continue;
		}
		if((s[r]^s[l-1])!=0)
		{
			printf("-1\n");
			continue;
		}
		if((r-l+1)%2==0)//询问子区间长度为偶数
		{
			if(a[l]==0||a[r]==0)//两头任一为0,转化成长度为奇数的情况,只需全异或一遍
			{
				printf("1\n");
				continue;
			}
			if(solve(l,r)!=-1)
			{
				printf("2\n");
			}
			else printf("-1\n");
		}
		else
		{
			printf("1\n");
		}
	}
		
	return 0;
}
posted @ 2022-11-08 14:04  LZH_03  阅读(31)  评论(0编辑  收藏  举报