P4428-[BJOI2018]二进制【树状数组,set】

正题

题目链接:https://www.luogu.com.cn/problem/P4428


题目大意

长度为\(n\)\(0/1\)串要求支持

  1. 修改一个位置
  2. 求区间\([l,r]\)有多少个子区间重排后的二进制数可以被三整除

\(1\leq n\leq 10^5\)


解题思路

首先有\(2^{2k}\%3=1(k\in Z)\)\(2^{2k+1}\%3=2(k\in Z)\)
分三种情况考虑

  • \(1\)\(1\)那么显然无论如何都不可以被三整除
  • \(2k\)\(1\)那么我们之间都排在最后面就好了。
  • \(2k+1\)\(1\)\(k\)不能为\(0\)),那么有一种方案就是把某个在奇数位置的\(1\)放到偶数位置就可以了,此时需要区间的长度至少为\(2k+3\)

然后具体分析一下相当于一个区间\(1\)的个数不能为\(1\)且如果是奇数个那么必须至少有两个\(0\)

看起来很复杂可以反过来做分成以下情况

  1. 区间全是\(1\)且长度为奇数
  2. 区间有一个\(0\)且长度为偶数
  3. 区间只有一个\(1\)
  4. 由于\(2\)\(3\)会重复一种只有一个\(1\)和一个\(0\)的情况所以需要加回这个方案

第四种是最好维护的,顺便用树状数组记录就好了

然后前三种我们对于\(0/1\)的位置分别开一个\(set\)来查询某个位置前驱/后继的0/1。

然后第三种情况我们对于每个\(1\)考虑左右的\(0\)区间然后记录在树状数组\(1\)的位置

对于第二种情况我们考虑对于每个\(0\)考虑左右的\(1\)然后记录在那个\(0\)的位置

对于第一种情况我们之间记录到区间最左端的\(0\)处。

然后统计答案的时候要记得把边界的情况考虑

写起来有点麻烦

时间复杂度\(O(n\log n)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#define lowbit(x) (x&-x)
#define ll long long
using namespace std;
const ll N=1e5+10;
ll n,m,a[N],t[N],p[N];
set<ll> s[2];
void Change(ll x,ll val){
	while(x<=n){
		t[x]+=val;
		x+=lowbit(x);
	}
	return;
}
ll Ask(ll x){
	ll ans=0;
	while(x){
		ans+=t[x];
		x-=lowbit(x);
	}
	return ans;
}
ll Left(ll op,ll x)
{return (*--s[op].upper_bound(x));}
ll Right(ll op,ll x)
{return (*s[op].lower_bound(x));}
ll Count(ll n)
{return (n+1)/2*(n+2-(n&1))/2;}
ll Caunt(ll n)
{return n*(n+1)/2;}
ll Calc(ll L,ll R)
{return (L/2+1)*((R+1)/2)+((L+1)/2)*(R/2+1);}
void Updata(ll x){
	if(x<1||x>n)return;
	if(p[x])Change(x,-p[x]);
	if(a[x]){
		ll L=(x-Left(1,x-1)-1),R=(Right(1,x+1)-x-1);
		p[x]=(L+1)*(R+1)-1;
	}
	else{
		ll L=(x-Left(0,x-1)-1),R=(Right(0,x+1)-x-1);
		p[x]=Calc(L,R)+Count(R);
	}
	if(x<n&&a[x]!=a[x+1])p[x]--;
	Change(x,p[x]);
	return;
}
ll Get(ll x,ll l,ll r){
	ll L=max(Left(0,x-1),l-1),R=min(Right(0,x+1),r+1);
	L=x-L-1;R=R-x-1;
	return Calc(L,R);
}
ll Qet(ll x,ll l,ll r){
	ll L=max(Left(1,x-1),l-1),R=min(Right(1,x+1),r+1);
	L=x-L-1;R=R-x-1;
	return (L+1)*(R+1)-1;
}
signed main()
{
	scanf("%lld",&n);
	s[0].insert(0);s[0].insert(n+1);
	s[1].insert(0);s[1].insert(n+1);
	for(ll i=1;i<=n;i++)
		scanf("%lld",&a[i]),s[a[i]].insert(i);
	for(ll i=1;i<=n;i++)
		Updata(i);
	scanf("%lld",&m);
	while(m--){
		ll op,l,r,x;
		scanf("%lld",&op);
		if(op==1){
			scanf("%lld",&x);
			s[a[x]].erase(x);
			a[x]=!a[x];
			s[a[x]].insert(x);
			Updata(x);
			Updata(Left(0,x-1));
			Updata(Left(1,x-1));
			Updata(Right(0,x+1));
			Updata(Right(1,x+1));
		}
		else{
			scanf("%lld%lld",&l,&r);
			ll ans=(r-l+1)*(r-l+2)/2;
			if(Left(1,r)<l){printf("%lld\n",ans);continue;}
			if(Left(0,r)<l){ans-=Count(r-l+1);printf("%lld\n",ans);continue;}
			ans-=Ask(r)-Ask(l-1);
			if(r<n&&a[r]!=a[r+1])ans--;
			ll Ll=Left(0,l-1),Rr=Right(0,r+1),Lr=Left(0,r),Rl=Right(0,l);
			ans=ans+Get(Rl,1,n)-Get(Rl,l,r);
			if(Lr!=Rl)ans=ans+Get(Lr,1,n)-Get(Lr,l,r);
			if(a[r+1])ans=ans+Count(Rr-Lr-1)-Count(r-Lr);
			if(a[l])ans=ans-Count(Rl-l);
			
			Ll=Left(1,l),Rr=Right(1,r),Lr=Left(1,r),Rl=Right(1,l);
			ans=ans+Qet(Rl,1,n)-Qet(Rl,l,r);
			if(Lr!=Rl)ans=ans+Qet(Lr,1,n)-Qet(Lr,l,r);
//			if(!a[r])ans=ans+Caunt(Rr-Rl-1)-Caunt(r-Rl);
//			if(!a[l])ans=ans-Caunt(Lr-l);
			
			printf("%lld\n",ans);
		}
	}
	return 0;
}
posted @ 2021-07-12 16:09  QuantAsk  阅读(75)  评论(0编辑  收藏  举报