codeforces 1030E Vasya and Good Sequences

题解

很容易观察到的两点是, a i a_i ai有用的只有二进制表示下的1的个数,那么既然只有个数有用,如果某个区间的1的个数的和是偶数的话,会有很大可能是好序列,在验证过程中可以发现,1 1 4这种序列是不可以的,因为4*2>6,简单的说,序列总和必须要至少为其中最大的数的两倍,那么就转化为新的问题:

求一个序列中有多少连续子序列的和是偶数,并且和不小于最大值的两倍

(我的做法可能有点复杂,细节很多)
第二个限制非常难搞,但是我们注意到,新的序列 b i b_i bi中,最大值大约是60多。可不可以枚举最大值?可不可以把区间按最大值分类?假设枚举出一个最大值M,序列(指新序列,下同)中某些位置是等于M的,我们要考虑的是跨越了至少一个这些位置,并且没有包含比M大的数的区间。假设b[cur]=M,枚举i>=cur作为区间右端点,(因为最多枚举60次最大值,所以这部分枚举的复杂度是O(60n),是可以接受的)我们要快速找有多少个合法左端点。
考虑怎么搞掉限制2就好了,如果右端点是 j j j左端点是 i i i,那么是要求 S u m b ( j ) − S u m b ( i − 1 ) > = M ∗ 2 Sum_b(j)-Sum_b(i-1)>=M*2 Sumb(j)Sumb(i1)>=M2( * )。我们注意到题中的 a i > = 1 a_i>=1 ai>=1也就是说 b i > = 1 b_i>=1 bi>=1加上M最大为60,这意味着,我最多从j暴力往前找60*2=120个位置,( * )式必然能够被满足,然后就只需要考虑在此位置之前 S u m b ( i ) Sum_b(i) Sumb(i)有多少个和 S u m b ( j ) Sum_b(j) Sumb(j)奇偶性一样的就可以了。

这是大体框架,其次还需要注意不能包含比M大的数值,需要在细节上加一些处理。

代码

//QWsin
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define out(i,u) for(int i=first[u];i!=-1;i=next[i])
#define repvc(i,vc) for(int i=0,Sz=vc.size();i<Sz;++i)
typedef long long ll;
inline ll read()
{
	char ch=getchar();ll ret=0,f=1;
	while(ch<'0' || ch>'9') {if(ch=='-')f=-1;ch=getchar();}
	for(;ch>='0' && ch<='9';ch=getchar()) ret=ret*10+ch-'0';
	return ret*f;
}
using namespace std;

const int INF=1<<30;

const int maxn=300000+10;

int a[maxn],sum[maxn],ji[maxn],ou[maxn],t[maxn];

inline int cal(ll x)
{
	int ret=0;
	for(;x;x>>=1) ret+=x&1ll;
	return ret;	
}

inline int check(int x){return !(x&1);}
inline int s_ji(int l,int r){return ji[r]-(l==0?0:ji[l-1]);}
inline int s_ou(int l,int r){return ou[r]-(l==0?0:ou[l-1]);}

int main()
{
	int n;cin>>n;
	
	ou[0]=1;
	rep(i,1,n) {
		a[i]=cal(read()),sum[i]=sum[i-1]+a[i];
		ji[i]=(sum[i]&1)+ji[i-1];
		ou[i]=!(sum[i]&1)+ou[i-1];t[i]=a[i];
	}
	
	sort(t+1,t+n+1);
	long long ans=0;
	int k=unique(t+1,t+n+1)-t-1;
	rep(o,1,k)
	{
		int M=t[o],cur=1,pre=0;
		while(a[cur]!=M) {
			if(a[cur]>M) pre=cur;
			++cur;
		}
		while(cur<=n)
		{
			while(cur<=n&&a[cur]!=M) {
				if(a[cur]>M) pre=cur;
				++cur;
			}
			
			int j;
			for(j=cur;(a[j]<M ||j==cur)&& j<=n;++j)
			{
				int pos=cur-1;
				while(pos>=pre && sum[j]-sum[pos]<M*2) --pos;
				if(pos<pre) continue;
				
				if(sum[j]&1) ans+=s_ji(pre,pos);
				else ans+=s_ou(pre,pos);
			}
			cur=j;
		}
	}
	
	cout<<ans<<endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值