1. 程式人生 > >Codeforces 875D High Cry [列舉+二進位制]

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);
}