帶修莫隊學習筆記+HDU6610

帶修莫隊

  • 允許離線的帶區間修改的操作
    -將修改操作編號,稱爲"時間戳"

普通莫隊是不能帶修改的,我們可以強行讓它可以修改

可以強行加上一維時間維, 表示這次操作的時間

  • [l1,r,time][l−1, r, time]
  • [l+1,r,time][l + 1, r, time]
  • [l,r1,time][l, r - 1, time]
  • [l,r+1,time][l, r + 1, time]
  • [l,r,time1][l, r, time - 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];

複雜度


一般用的分塊大小爲n23n^\frac{ 2 }{3}

可以用和普通莫隊類似的方法排序轉移,做到 O(n53)O(n^ { \frac{5}{3} })

這一次我們排序的方式是以 n23n^{\frac{ 2 }{3}}爲一塊,分成了 n13n^{\frac{ 1 }{3}}

第一關鍵字是左端點所在塊,第二關鍵字是右端點所在塊,第三關鍵字是時間。

還是來證明一下時間複雜度(默認塊大小爲n23n^\frac{ 2 }{3}):

  • 左右端點所在塊不變,時間在排序後單調向右移,這樣的複雜度是 O(n)O(n)
  • 若左右端點所在塊改變,時間一次最多會移動nn個格子,時間複雜度 O(n)O(n)
  • 左端點所在塊一共有 n13n^ { \frac{1}{3} } 中,右端點也是` n13n^ { \frac{1}{3} }
  • 一共 n13×n13=n23{ n ^ {\frac{1}{3}} }\times{ n ^ {\frac{1}{3}} } = n ^ { \frac{2}{3} }種,每種乘上移動的複雜度 $O(n) $,總複雜度 O(n53)O(n^ { \frac{5}{3} })

代碼

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

題意

你有nn堆石子,每堆石子有aia_{i}個石子

遊戲規則:

  • Alice 先選擇一個大範圍[L,R][L,R]區間內的石子

  • Bob選擇一個子區間[l,r][l,r]內的石子最終進行遊戲

  • 每次至少取走某一堆的一個石子,至多全部取走,無法移動石子者輸(Nim 博弈)

    Alice先手,雙方足夠聰明

    問對Alice的每次選擇[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]);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章