Codeforces 875D High Cry [枚举+二进制]

题意:给你n个数,求区间满足区间或大于等于区间最大值。

题解:对于区间个数的问题,一般可能涉及到二分、尺取、或者枚举等。这题首先,我们考虑所有区间的个数n*(n+1)/2,我们可以通过减去不符合条件的区间来计算符合条件的区间,然后枚举区间最大值,对于当前这个数,我们找右边小于等于他的最右边界和左边小于他的最左边界(右等左不等,防止相同数的时候重复计算)。我们可以通过set将数字从大到小加入,然后找到这个区间。

对于这个数二进制每一位,假如要对区间值产生影响的话,那必须是最大值的数这一位上为0,其他数当前位置存在1,不符合条件的情况是左右最近的位置上的1的位置。

最后将二进制每一位得到的不符合条件的区间求交,最后区间(I-L+1)*(R-i+1)就是不符合条件的区间个数。

AC代码:

#include<stdio.h>
#include<algorithm>
#include<set>
using namespace std;
typedef long long ll;
ll a[200005];
ll s[200005];
ll pre[200005][35];
ll last[200005][35];
ll L[200005],R[200005];
set<ll>st;
set<ll>::iterator it;
bool cmp(ll A,ll B)
{
	if(a[A]==a[B])return A<B;
	return a[A]>a[B];
}
int main()
{
	ll n;
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);s[i]=i;
		ll now=a[i];
		for(ll j=0;j<=32;j++)
		{
			if(now%2==1)pre[i][j]=i;
			else pre[i][j]=pre[i-1][j];
			now/=2;
		}
	}
	for(ll i=0;i<=32;i++)
		last[n+1][i]=n+1;
	for(ll i=n;i>=1;i--)
	{
		ll now=a[i];
		for(ll j=0;j<=32;j++)
		{
			if(now%2==1)last[i][j]=i;
			else last[i][j]=last[i+1][j];
			now/=2;
		}
	}
	sort(s+1,s+n+1,cmp);
	st.insert(0);
	st.insert(n+1);
	ll ans=n*(n+1)/2;
	for(ll i=1;i<=n;i++)
	{
		it=st.insert(s[i]).first;
		it--;
		L[s[i]]=*it;
		it++;it++;
		R[s[i]]=*it;
		ll l=L[s[i]],r=R[s[i]];
		if(a[l]==a[s[i]])L[s[i]]=L[l],R[l]=min(R[l],s[i]);
	}
	for(ll i=1;i<=n;i++)
	{
		ll ansl=L[i]+1,ansr=R[i]-1;
		for(ll j=0;j<=32;j++)
		{
			if(pre[i][j]==i)continue;
			ansl=max(ansl,pre[i][j]+1);
		}
		for(ll j=0;j<=32;j++)
		{
			if(last[i][j]==i)continue;
			ansr=min(ansr,last[i][j]-1);
		}
		ans-=(i-ansl+1)*(ansr-i+1);
	}
	printf("%lld\n",ans);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值