取反(分塊+二分)

cn是大帅哥886發表於2024-03-12

給一個長度是n的陣列,a[1],a[2],a[3],...a[n-1],a[n],初始時a陣列中所有的元素都為0,下面有兩種操作:

1.指定一個區間[x,y], 把a[x],a[x+1],...a[y]的值取反,即如果a[i]的值為1則把a[i]的值變為0,如果a[i]的值為0則把a[i]的值變為1

2.指定一個區間[x,y], 求a[x],a[x+1],...a[y]中有多少個值為1的元素

輸入格式

第一行兩個整數n和m,分別表示陣列a的長度和操作次數

接下來m行,每行三個整數op,x,y

如果op=0,則此時進行第一種操作

如果op=1,則此時進行第二種操作

2<=n<=1e5,1<=m<=1e5,1<=x,y<=n, 0<=op<=1

輸出格式

對於每次的第二種操作,輸出一個整數,表示區間中值是1的個數

輸入/輸出例子1

輸入:

4 5

0 1 2

0 2 4

1 2 3

0 2 4

1 1 4

輸出:

1

2

樣例解釋

原題:https://www.luogu.com.cn/problem/P3870

比較噁心,總體還是可以用分塊思想

操作1

中間段開陣列標記(注意,如果被改2次,相當於沒改,取反即可0變1,1變0),兩邊直接改(改的時候也要看看標記陣列),然後開一個陣列維護區間和

void change(int L, int R)
{
	int p=pos[L], q=pos[R];
	if (p==q)
	{
		for (int i=L; i<=R; i++)
		{
			if (a[i]==1) a[i]=0, sum[p]--; //改0,數量減一
			else a[i]=1, sum[p]++; //改1,數量加一
		}
	}
	else
	{
		for (int i=p+1; i<=q-1; i++) add[i]^=1; //標記陣列
		for (int i=L; i<=ed[p]; i++)
		{
			if (a[i]==1) a[i]=0, sum[p]--;
			else a[i]=1, sum[p]++;
		}
		for (int i=st[q]; i<=R; i++) 
		{
			if (a[i]==1) a[i]=0, sum[q]--;
			else a[i]=1, sum[q]++;
		}
	}
}

  

操作2

兩邊段有4種情況

1.標記陣列=1,本身數字=0(此時要轉換成開燈,答案肯定是累加的)

2.標記陣列=1,本身數字=1(此時要轉換成關燈,答案肯定無用)

3.標記陣列=0,本身數字=1(此時就是開燈,答案肯定是累加的)

4.標記陣列=0,本身數字=0(此時就是關燈,答案肯定無用)

中間段直接看標記,如果是1,就是區間關燈數量(取反),如果是0,就是開燈數量(取反)

區間關燈數量=區間長度減去當前區間開燈數

int query(int L, int R)
{
	int p=pos[L], q=pos[R], ans=0, tmp=0;
	if (p==q)
	{
		for (int i=L; i<=R; i++)
			if ((a[i]==1 && add[p]==0) || (a[i]==0 && add[p]==1)) ans++;
	}
	else
	{
		for (int i=p+1; i<=q-1; i++)
		{
			if (add[i]) ans+=(ed[i]-st[i]+1)-sum[i];
			else ans+=sum[i];
		}
		for (int i=L; i<=ed[p]; i++)
			if ((a[i]==1 && add[p]==0) || (a[i]==0 && add[p]==1)) ans++;
		for (int i=st[q]; i<=R; i++) 
			if ((a[i]==1 && add[q]==0) || (a[i]==0 && add[q]==1)) ans++;
	}
	return ans;
}

  、

另外,每次查詢完,標記陣列不能清零,畢竟可能是要查詢的區間是一小段,但這一段的其他元素也要用到這個標記陣列,只要維護了sum,就可以了

程式碼

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

const int N=1e5+5;
int n, m, op, x, y, a[N], sum[N], add[N], pos[N], st[N], ed[N];
void init()
{
	int block=sqrt(n);
	int t=n/block;
	
	if (n%block) t++;
	
	for (int i=1; i<=t; i++)
	{
		st[i]=(i-1)*block+1;
		ed[i]=i*block;
	}
    ed[t]=n;
	for (int i=1; i<=n; i++) pos[i]=(i-1)/block+1;
}
void change(int L, int R)
{
	int p=pos[L], q=pos[R];
	if (p==q)
	{
		for (int i=L; i<=R; i++)
		{
			if (a[i]==1) a[i]=0, sum[p]--;
			else a[i]=1, sum[p]++;
		}
	}
	else
	{
		for (int i=p+1; i<=q-1; i++) add[i]^=1;
		for (int i=L; i<=ed[p]; i++)
		{
			if (a[i]==1) a[i]=0, sum[p]--;
			else a[i]=1, sum[p]++;
		}
		for (int i=st[q]; i<=R; i++) 
		{
			if (a[i]==1) a[i]=0, sum[q]--;
			else a[i]=1, sum[q]++;
		}
	}
}
int query(int L, int R)
{
	int p=pos[L], q=pos[R], ans=0, tmp=0;
	if (p==q)
	{
		for (int i=L; i<=R; i++)
			if ((a[i]==1 && add[p]==0) || (a[i]==0 && add[p]==1)) ans++;
	}
	else
	{
		for (int i=p+1; i<=q-1; i++)
		{
			if (add[i]) ans+=(ed[i]-st[i]+1)-sum[i];
			else ans+=sum[i];
		}
		for (int i=L; i<=ed[p]; i++)
			if ((a[i]==1 && add[p]==0) || (a[i]==0 && add[p]==1)) ans++;
		for (int i=st[q]; i<=R; i++) 
			if ((a[i]==1 && add[q]==0) || (a[i]==0 && add[q]==1)) ans++;
	}
	return ans;
}
signed main()
{
	scanf("%d%d", &n, &m);
	init();
	
	while (m--)
	{
		scanf("%d%d%d", &op, &x, &y);
		if (op==0)
		{
			change(x, y);
		}
		else if (op==1)
		{
			printf("%lld\n", query(x, y));
		}
	} 
	return 0;
}