带修莫队学习笔记+HDU6610

带修莫队

  • 允许离线的带区间修改的操作
    -将修改操作编号,称为"时间戳"

普通莫队是不能带修改的,我们可以强行让它可以修改

可以强行加上一维时间维, 表示这次操作的时间

  • [ l − 1 , r , t i m e ] [l−1, r, time] [l1,r,time]
  • [ l + 1 , r , t i m e ] [l + 1, r, time] [l+1,r,time]
  • [ l , r − 1 , t i m e ] [l, r - 1, time] [l,r1,time]
  • [ l , r + 1 , t i m e ] [l, r + 1, time] [l,r+1,time]
  • [ l , r , t i m e − 1 ] [l, r, time - 1] [l,r,time1]
  • [ l , r , t i m e + 1 ] [l, r, time + 1] [l,r,time+1]

排序

形如普通莫队

struct Node {
	int left;
	int right;
	int time;
	int id;
	friend bool operator <(const Node& a, const Node& b) {
		return belong[a.left] ^ belong[b.left] ? belong[a.left] < belong[b.left] :
			//先按左区间为第一关键字排序
			belong[a.right] ^ belong[b.right] ? belong[a.right] < belong[b.right] :
			//再按右区间为第二关键字排序
			a.time < b.time;
		//最后按时间排序
	}
}q[maxn];

复杂度


一般用的分块大小为 n 2 3 n^\frac{ 2 }{3} n32

可以用和普通莫队类似的方法排序转移,做到 O ( n 5 3 ) O(n^ { \frac{5}{3} }) O(n35)

这一次我们排序的方式是以 n 2 3 n^{\frac{ 2 }{3}} n32为一块,分成了 n 1 3 n^{\frac{ 1 }{3}} n31

第一关键字是左端点所在块,第二关键字是右端点所在块,第三关键字是时间。

还是来证明一下时间复杂度(默认块大小为 n 2 3 n^\frac{ 2 }{3} n32):

  • 左右端点所在块不变,时间在排序后单调向右移,这样的复杂度是 O ( n ) O(n) O(n)
  • 若左右端点所在块改变,时间一次最多会移动 n n n个格子,时间复杂度 O ( n ) O(n) O(n)
  • 左端点所在块一共有 n 1 3 n^ { \frac{1}{3} } n31 中,右端点也是` n 1 3 n^ { \frac{1}{3} } n31
  • 一共 n 1 3 × n 1 3 = n 2 3 { n ^ {\frac{1}{3}} }\times{ n ^ {\frac{1}{3}} } = n ^ { \frac{2}{3} } n31×n31=n32种,每种乘上移动的复杂度 $O(n) $,总复杂度 O ( n 5 3 ) O(n^ { \frac{5}{3} }) O(n35)

代码

Node

增加时间戳

int block, belong[maxn];
struct Node {
	int left;
	int right;
	int time;	//增加时间维
	int id;
	friend bool operator <(const Node& a, const Node& b) {
		return belong[a.left] ^ belong[b.left] ? belong[a.left] < belong[b.left] :
			belong[a.right] ^ belong[b.right] ? belong[a.right] < belong[b.right] :
			a.time < b.time;
	}
}q[maxn];
读入

一定要另开一个数组模拟改变的过程

for (int i = 1; i <= m; i++) {
	scanf("%s", p);
	if (p[0] == 'Q') {
		q[++cnt].left = read();
		q[cnt].right = read();
		q[cnt].id = cnt;
		q[cnt].time = t;
	}
	else {		//设b数组存改变后的值,存Modify里
		modify[++t].position = read();
		modify[t].now = read();
		modify[t].pre = b[modify[t].position];
		b[modify[t].position] = modify[t].now;
	}
}
莫队
register int stdl = 1, stdr = 0, stdt = 0, ans = 0, position;
//左指针、右指针、时间指针
for (int i = 1; i <= cnt; i++) {
	while (stdl > q[i].left) {
		stdl--;
		//operation 添加左端点
	}
	while (stdr < q[i].right) {
		stdr++;
		//operation 添加右端点
	}
	while (stdl < q[i].left) {
		//operation 删除左端点
		stdl++;
	}
	while (stdr > q[i].right) {
		//operation 删除右端点
		stdr--;
	}
	while (stdt < q[i].time) {
		stdt++;
		position = modify[stdt].position;
		//operation 修改位置
		if (position >= stdl && position <= stdr) {
			//operation 删除原贡献
			//operation 增加新贡献
		}
	}
	while (stdt > q[i].time) {
		position = modify[stdt].position;
		//operation 变回原来
		if (position >= stdl && position <= stdr) {
			//operation 删除新贡献
			//operation 增加原贡献
		}
		stdt--;
	}
	res[q[i].id] = ans;
}

模板题

P1903 数颜色 / 维护队列

Game

题意

你有 n n n堆石子,每堆石子有 a i a_{i} ai个石子

游戏规则:

  • Alice 先选择一个大范围 [ L , R ] [L,R] [L,R]区间内的石子

  • Bob选择一个子区间 [ l , r ] [l,r] [l,r]内的石子最终进行游戏

  • 每次至少取走某一堆的一个石子,至多全部取走,无法移动石子者输(Nim 博弈)

    Alice先手,双方足够聪明

    问对Alice的每次选择 [ L i , R i ] [Li,Ri] [Li,Ri],Bob有多少种选择能让Alice必胜

  • 修改操作,即交换相邻的两堆石子

思路

Nim博弈

易知为他们进行的游戏为Nim博弈

要使Alice获胜,只需要区间异或和不为0即可

那么我们就可以去求他的补集,区间异或和为0的情况

带修莫队
  • 数据范围为1e5,时间10s,可以接受带修莫队
  • 单点修改,查询区间异或相同的个数,均可以由带修莫队实现
  • 区间异或和为0的个数即为相同的前缀异或和对数
    因为计算前缀和的对数,所以要 left - 1,脑模一下就知道了
  • 单点修改造成影响只对前一个的前缀和产生影响

代码

时间戳
struct Time {
	int x;		//修改位置
	int pre;	//原值
	int now;	//新值
}t[maxn];
离线询问
int block, belong[maxn];
struct Node {
	int left;
	int right;
	int time;
	int id;
	friend bool operator <(const Node& a, const Node& b) {
		return belong[a.left] == belong[b.left] ?(belong[a.right] == belong[b.right] ? 
            a.time < b.time : 		//time为第三关键字
            a.right < b.right) :	//right为第二关键字
			a.left < b.left;		//left为第一关键字
	}
}q[maxn];
预处理
block = (int)pow(n, 2. / 3);	//带修莫队,常用block
for (int i = 1; i <= n; i++) {
	scanf("%d", &a[i]); 
    b[i] = a[i];
	belong[i] = i / block;	//预处理block
	sum[i] = sum[i - 1] ^ a[i];//前缀和
	r_sum[i] = sum[i];			//模拟前缀和
}
int cnt = 0, opt, T = 0;
for (int i = 1; i <= m; i++) {
	scanf("%d", &opt);
	if (opt == 1) {
		scanf("%d%d", &q[++cnt].left, &q[cnt].right);
		q[cnt].left--;
		q[cnt].time = T;
		q[cnt].id = cnt;
	}
	else {
		scanf("%d", &t[++T].x);		//在b[]和r_sum[]模拟修改,记录
		t[T].pre = r_sum[t[T].x];	
		t[T].now = r_sum[t[T].x] = r_sum[t[T].x + 1] ^ b[t[T].x];
		swap(b[t[T].x], b[t[T].x + 1]);
	}
}
sort(q + 1, q + 1 + cnt);
莫队
int stdl = 1, stdr = 0, stdt = 0; LL ans = 0;
register int position;
for (int i = 1; i <= cnt; i++) {
	while (stdl > q[i].left) ans = ans + col[sum[--stdl]]++;
	while (stdl < q[i].left) ans = ans - --col[sum[stdl++]];
	while (stdr < q[i].right) ans = ans + col[sum[++stdr]]++;
	while (stdr > q[i].right) ans = ans - --col[sum[stdr--]];
	while (stdt < q[i].time) {
		position = t[++stdt].x;
		sum[position] = t[stdt].now;	//更改值
		if (position >= q[i].left && position <= q[i].right) {
			ans = ans - --col[t[stdt].pre];	//修改贡献
			ans = ans + col[t[stdt].now]++;
		}
	}
	while (stdt > q[i].time) {
		position = t[stdt].x;
		sum[position] = t[stdt].pre;	//更改值
		if (position >= q[i].left && position <= q[i].right) {
			ans = ans - --col[t[stdt].now];//修改贡献
			ans = ans + col[t[stdt].pre]++;
		}
		stdt--;
	}
	res[q[i].id] = LL(q[i].right - q[i].left + 1) * (q[i].right - q[i].left) / 2 - ans;			//取补集
}
AC
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 5;
const int maxm = 1e6 + 5e4 + 5;
struct Time {
	int x;
	int pre;
	int now;
}t[maxn];
int block, belong[maxn];
struct Node {
	int left;
	int right;
	int time;
	int id;
	friend bool operator <(const Node& a, const Node& b) {
		return belong[a.left] == belong[b.left] ?
			(belong[a.right] == belong[b.right] ? a.time < b.time : a.right < b.right) :
			a.left < b.left;
	}
}q[maxn];
int a[maxn], b[maxn], sum[maxn], r_sum[maxn];
int col[maxm];
LL res[maxn];
int main() {
	int n, m;
	while (~scanf("%d%d", &n, &m)) {
		memset(col, 0, sizeof(col));
		block = (int)pow(n, 2. / 3);
		for (int i = 1; i <= n; i++) {
			scanf("%d", &a[i]); b[i] = a[i];
			belong[i] = i / block;
			sum[i] = sum[i - 1] ^ a[i];
			r_sum[i] = sum[i];
		}
		int cnt = 0, opt, T = 0;
		for (int i = 1; i <= m; i++) {
			scanf("%d", &opt);
			if (opt == 1) {
				scanf("%d%d", &q[++cnt].left, &q[cnt].right);
				q[cnt].left--;
				q[cnt].time = T;
				q[cnt].id = cnt;
			}
			else {
				scanf("%d", &t[++T].x);
				t[T].pre = r_sum[t[T].x];
				t[T].now = r_sum[t[T].x] = r_sum[t[T].x + 1] ^ b[t[T].x];
				swap(b[t[T].x], b[t[T].x + 1]);
			}
		}
		sort(q + 1, q + 1 + cnt);
		int stdl = 1, stdr = 0, stdt = 0; LL ans = 0;
		register int position;
		for (int i = 1; i <= cnt; i++) {
			while (stdl > q[i].left) ans = ans + col[sum[--stdl]]++;
			while (stdl < q[i].left) ans = ans - --col[sum[stdl++]];
			while (stdr < q[i].right) ans = ans + col[sum[++stdr]]++;
			while (stdr > q[i].right) ans = ans - --col[sum[stdr--]];
			while (stdt < q[i].time) {
				position = t[++stdt].x;
				sum[position] = t[stdt].now;
				if (position >= q[i].left && position <= q[i].right) {
					ans = ans - --col[t[stdt].pre];
					ans = ans + col[t[stdt].now]++;
				}
			}
			while (stdt > q[i].time) {
				position = t[stdt].x;
				sum[position] = t[stdt].pre;
				if (position >= q[i].left && position <= q[i].right) {
					ans = ans - --col[t[stdt].now];
					ans = ans + col[t[stdt].pre]++;
				}
				stdt--;
			}
			res[q[i].id] = LL(q[i].right - q[i].left + 1) * (q[i].right - q[i].left) / 2 - ans;
		}
		for (int i = 1; i <= cnt; i++)
			printf("%lld\n", res[i]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值