HDU多校第三场 1008 Game —— 三维莫队 + 前缀异或

题目链接:点我啊╭(╯^╰)╮

题目大意:

     n n n 堆石块,每次可以从一堆中取至少一块,至多取完
     A l i c e Alice Alice 先手, q q q 次询问
    ①:询问 [ L , R ] [L,R] [L,R] 内有多少个子区间是 A l i c e Alice Alice 必胜
    ②:交换 a i a_i ai a i + 1 a_{i+1} ai+1

解题思路:

    根据 N i m Nim Nim 博弈,区间异或为 0 0 0 A l i c e Alice Alice 必败
    那么预处理出异或前缀,用三维莫队即可

     c n t [ x ] cnt[x] cnt[x] 表示当前区间内出现前缀为 x x x 的数量, b b b 为前缀
    对于 r r r 的转移,直接用 c n t [ b [ r ] ] cnt[b[r]] cnt[b[r]] 讨论即可
    对于 l l l 的转移,若用 c n t [ b [ l ] ] cnt[b[l]] cnt[b[l]],则不能讨论到整体区间 [ l , r ] [l,r] [l,r] 的情况
    所以对于每个询问区间的 l l l 都要减一


    时间戳修改可以在莫队中变换 a a a b b b 的数值
    也可以先全部变换完了之后从最后一个时间开始莫队
    因为如果从 t i m e = 0 time=0 time=0 开始进行莫队的话一定要确保对 a a a b b b 的修改

核心:用前缀异或性质 维护三维莫队

#include<bits/stdc++.h>
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int a[maxn], b[maxn], belong[maxn];
int cntq, cntc, n, m, size, bnum;
ll sum, cnt[1<<21], ans[maxn];

struct query {
	int l, r, time, id;
} q[maxn];
struct modify {
	int pos, pre, val;
} c[maxn];

int cmp(query a, query b) {
	return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] :\
	 ((belong[a.r] ^ belong[b.r]) ? belong[a.r] < belong[b.r] : a.time < b.time);
}

inline void add(int x){
	sum += cnt[x];
	cnt[x]++;
}

inline void del(int x){
	--cnt[x];
	sum -= cnt[x];
}

int main() {
	while(~scanf("%d%d", &n, &m)){
		size = pow(n, 2.0 / 3.0);
		bnum = ceil((double)n / size);
		for(int i=1; i<=bnum; ++i)
			for(int j=(i-1)*size+1; j<=i*size; ++j) 
				belong[j] = i;
		for(int i=1; i<=n; ++i){
			scanf("%d", a+i);
			b[i] = b[i-1] ^ a[i];
		}
		cntq = cntc = 0;
		for(int i=1; i<=m; ++i) {
			int op, pos;
			scanf("%d", &op);
			if(op == 1) {
				++cntq;
				scanf("%d%d", &q[cntq].l, &q[cntq].r);
				--q[cntq].l;
				q[cntq].time = cntc;
				q[cntq].id = cntq;
			} else {
				++cntc;
				scanf("%d", &pos);
				c[cntc].pos = pos;
				c[cntc].pre = b[pos];
				c[cntc].val = b[pos+1] ^ a[pos];
                b[pos] = b[pos+1] ^ a[pos];
                swap(a[pos], a[pos+1]);
			}
		}
		sort(q+1, q+cntq+1, cmp);
		memset(cnt, 0, sizeof(cnt)), sum = 0;
		int l = 1, r = 0, time = cntc, ql, qr, qt;
		for(int i = 1; i <= cntq; ++i) {
			ql = q[i].l, qr = q[i].r, qt = q[i].time;
			while(l < ql) del(b[l++]);
			while(l > ql) add(b[--l]);
			while(r < qr) add(b[++r]);
			while(r > qr) del(b[r--]);
			while(time < qt) {
				++time;
				if(ql <= c[time].pos && c[time].pos <= qr) {
					del(c[time].pre);
					add(c[time].val);
				}
				b[c[time].pos] = c[time].val;
			}
			while(time > qt) {
				if(ql <= c[time].pos && c[time].pos <= qr) {
					del(c[time].val);
					add(c[time].pre);
				}
				b[c[time].pos] = c[time].pre;
				--time;
			}
			ans[q[i].id] = 1ll*(qr-ql+1)*(qr-ql)/2 - sum;
		}
		for(int i=1; i<=cntq; ++i) printf("%lld\n", ans[i]);
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值