E-Sequence题解 (2023牛客暑期多校训练营6)

文章讨论了如何在给定数组的查询中,判断是否能将区间划分为偶数子区间。提出两种方法:一种是通过记录1的位置并二分查找优化,另一种是利用前缀和统计奇偶性。最终目标是达到O(n)或接近O(n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给定一个数组,有若干次查询,每次查询给出l,r,k表示查询的区间范围,问能否将查询的区间划分成k段子区间,并且每个子区间的区间和都是偶数。数据范围1e5。

基础想法:首先题目都是奇偶性,所以具体的值不重要,输入数组时可以直接按照0/1处理。并且因为要求划分为偶数区间,所以区间和是奇数的显然不行,查询的区间长度小于划分段数的也不行,这几种情况直接输出NO即可,重点是如何判断YES的情况。

思路1:首先可以发现一个性质,就是区间和为偶数的区间合并起来区间和还是偶数。所以问能否将区间划分为k段,就可以去找整个区间内最多有多少互不影响的偶数区间,那么这种区间的数量就等于单独的0作为一段,以及有一个1的话,一直找到它右边的另一个1,把整个看成一段。考虑暴力对每次查询的区间都扫一遍统计这样的数量明显复杂度是O(n²)的,考虑如何进行优化。这里我们发现,按这个思路找区间的话主要就是跟区间里的1的位置有关,有效的区间数量相当于总的0数量减去夹在1中间的0的数量。所以提前扫描数组,记录其中1的位置,然后对于查找的区间,去二分查找区间内最左边和最右边的1的位置,然后遍历这些1统计夹在中间的0的个数。虽然极端情况下如果数组全为1时这个也是O(n²)复杂度,但是好像没有出这样的数据,赛时让我卡过去了,所以也骄傲地放一下代码吧。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int a[N], qian[N];
vector<int>v;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	int n, q, l, r, k;
	while (t--)
	{
        v.clear();
		cin >> n >> q;
		for (int i = 1;i <= n;i++)
		{
			cin >> a[i];
			a[i] &= 1;
            if(a[i]) v.push_back(i);
		}
		for (int i = 0;i < q;i++)
		{
			cin >> l >> r >> k;
			if (k > r - l + 1) cout << "NO" << endl;
			else
			{
				if(lower_bound(v.begin(),v.end(),l)==v.end()) cout<<"YES"<<endl;
                else{
                    int pos1=lower_bound(v.begin(),v.end(),l)-v.begin();
                    int pos2=upper_bound(v.begin(),v.end(),r)-v.begin()-1;
                    if((pos2-pos1)%2==0) {cout<<"NO"<<endl;continue;}
                    int cnt=0;
                    for(int j=pos1;j<=pos2;j+=2){
                        cnt+=v[j+1]-v[j];
                    }
                    if(r-l+1-cnt>=k) cout<<"YES"<<endl;
                    else cout<<"NO"<<endl;
                }
			}
		}
	}
    system("pause");
	return 0;
}

思路2:同样是找偶数区间,不过找的方法更高明一些。我们可以发现,对于两个1,以及之间的若干0,有这样一个性质。考虑前缀和,当碰到1的时候,前缀和奇偶性改变,中间的0不影响前缀和奇偶性,当再碰到一个1时奇偶性刚好变回去了。所以一个区间内能够使区间和为偶数的点满足前缀和奇偶性与区间左端点前一个数的前缀和奇偶性相同。那么对前缀和的奇偶性再做一次前缀和,统计每一个数前面的前缀和为奇数的数量,和前面的前缀和为偶数的数量。对于查询的区间,查询区间内有多少前缀和奇偶性跟l-1处奇偶性相同的数量就是答案。这样的话O(n)预处理后就可以直接O(1)输出。代码如下:

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
using namespace std;


const int maxn=1e5+10;
int arr[maxn],sum[maxn];
int num[2][maxn];//记录前缀和中为奇数,偶数的个数
int read(){
    int x=0,f=0,ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return f?-x:x;
}
int main(){
    int t=read();
	while(t--){
		num[0][0]=num[1][0]=0;
		int n=read(),m=read();
		for(int i=1;i<=n;i++){
			arr[i]=read();
			arr[i]&=1;
			sum[i]=sum[i-1]+arr[i];
			if(sum[i]&1){
				num[1][i]=num[1][i-1]+1;
				num[0][i]=num[0][i-1];
			}
			else{
				num[0][i]=num[0][i-1]+1;
				num[1][i]=num[1][i-1];
			}
		}
		for(int i=1;i<=m;i++){
			int l=read(),r=read(),k=read();
			if((sum[r]-sum[l-1])&1) {cout<<"NO"<<endl;continue;}
			int flag=sum[l-1]&1;
			if(num[flag][r]-num[flag][l-1]>k-1) cout<<"YES"<<endl;
			else cout<<"NO"<<endl;
		}
	}
    system("pause");
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值