P4887 【模板】莫队二次离线(第十四分块(前体))

本文介绍了如何使用离线莫队算法解决区间内满足特定条件的元素个数的二次计数问题。通过预处理和区间贡献的计算,实现了O(Cn+m√n)的时间复杂度,其中C是组合数。代码实现包括了区间更新和查询的处理,适用于处理大量查询的场景。

P4887 【模板】莫队二次离线(第十四分块(前体))

Solution

简单学习了一下二次离线莫队,写了个板子题。

这题直接莫队时间复杂度为O(Cnn)O(Cn\sqrt n)O(Cnn),其中C=(14k)C=\binom{14}{k}C=(k14),显然不太行。

我们考虑当前区间为[l,r][l,r][l,r],右端点增加kkk,变为[l,r+k][l,r+k][l,r+k]产生的贡献:

f(x,[l,r])=[∑i=lrpopcount(x  xor  ai)=k]f(x,[l,r])=[\sum_{i=l}^rpopcount(x\;xor\;a_i)=k]f(x,[l,r])=[i=lrpopcount(xxorai)=k]

则有:
Δans\Delta ansΔans

=∑i=r+1r+kf(i,[l,i−1])=\sum_{i=r+1}^{r+k}f(i,[l,i-1])=i=r+1r+kf(i,[l,i1])

=∑i=r+1r+kf(i,[1,i−1])−f(i,[1,l−1])=\sum_{i=r+1}^{r+k} f(i,[1,i-1])-f(i,[1,l-1])=i=r+1r+kf(i,[1,i1])f(i,[1,l1])

于是我们可以预处理f(i,[1,i−1])f(i,[1,i-1])f(i,[1,i1]),并且离线求出∑i=r+1r+kf(i,[1,l−1])\sum_{i=r+1}^{r+k}f(i,[1,l-1])i=r+1r+kf(i,[1,l1])。这个可以在O(Cn+mn)O(Cn+m\sqrt n)O(Cn+mn)的时间内完成。

其他端点的移动同理。

总时间复杂度为O(Cn+mn)O(Cn+m\sqrt n)O(Cn+mn)

Code

const int MX = 1 << 14;
int b[MX], sum[MX], a[MAXN], sz, bnum = 0, Cnum = 0, n, m, K;
ll s0[MAXN], s1[MAXN], Ans[MAXN];
struct Cnode{ int l, r, x, id; ll ans; } C[MAXN << 1];
struct Qnode{ int l, r, id; } Q[MAXN];

void Init() {
	for (int i = 0; i < MX ; ++ i) 
		if (__builtin_popcount(i) == K) b[++ bnum] = i;

	sort(Q + 1, Q + m + 1, [&](Qnode a, Qnode b){ return ((a.l - 1) / sz < (b.l - 1) / sz) || ((a.l - 1) / sz == (b.l - 1) / sz && a.r < b.r); });
	for (int i = 1, l = 1, r = 0; i <= m ; ++ i) {
		if (r < Q[i].r) ++ Cnum, C[Cnum] = (Cnode){r + 1, Q[i].r, l - 1, Cnum, 0}, r = Q[i].r;
		if (r > Q[i].r) ++ Cnum, C[Cnum] = (Cnode){Q[i].r + 1, r, l - 1, Cnum, 0}, r = Q[i].r;
		if (l < Q[i].l) ++ Cnum, C[Cnum] = (Cnode){l, Q[i].l - 1, r, Cnum, 0}, l = Q[i].l;
		if (l > Q[i].l) ++ Cnum, C[Cnum] = (Cnode){Q[i].l, l - 1, r, Cnum, 0}, l = Q[i].l;
	}
}
void Work() {
	sort(C + 1, C + Cnum + 1, [&](Cnode a, Cnode b){ return a.x < b.x; });
	for (int i = 0; i < MX ; ++ i) sum[i] = 0;
	for (int i = 1, nw = 0; i <= Cnum ; ++ i) {
		while (nw < C[i].x) {
			++ nw;
			for (int j = 1; j <= bnum ; ++ j) ++ sum[a[nw] ^ b[j]];
		}
		int Sum = 0;
		for (int j = C[i].l; j <= C[i].r ; ++ j) Sum += sum[a[j]];
		C[i].ans = Sum;
	} 
	for (int i = 0; i < MX ; ++ i) sum[i] = 0;
	for (int i = 1; i <= n ; ++ i) {
		s0[i] = s0[i - 1] + sum[a[i]];
		for (int j = 1; j <= bnum ; ++ j) ++ sum[a[i] ^ b[j]];
		s1[i] = s1[i - 1] + sum[a[i]];
	}
}
void Solve() {
	sort(C + 1, C + Cnum + 1, [&](Cnode a, Cnode b){ return a.id < b.id; });
	for (int i = 1, l = 1, r = 0, nw = 0; i <= m ; ++ i) {
		Ans[Q[i].id] = Ans[Q[i - 1].id];
		if (r < Q[i].r) ++ nw, Ans[Q[i].id] += (s0[Q[i].r] - s0[r]) - C[nw].ans, r = Q[i].r;
		if (r > Q[i].r) ++ nw, Ans[Q[i].id] -= (s0[r] - s0[Q[i].r]) - C[nw].ans, r = Q[i].r;
		if (l < Q[i].l) ++ nw, Ans[Q[i].id] += (s1[Q[i].l - 1] - s1[l - 1]) - C[nw].ans, l = Q[i].l;
		if (l > Q[i].l) ++ nw, Ans[Q[i].id] -= (s1[l - 1] - s1[Q[i].l - 1]) - C[nw].ans, l = Q[i].l;
	}
}
signed main() {
#ifndef ONLINE_JUDGE
	freopen("a.in", "r", stdin);
#endif
	read(n), read(m), read(K), sz = (int)sqrt(n);
	for (int i = 1; i <= n ; ++ i) read(a[i]);
	for (int i = 1; i <= m ; ++ i) read(Q[i].l), read(Q[i].r), Q[i].id = i;
	
	Init();
	Work();
	Solve();

	for (int i = 1; i <= m ; ++ i) print(Ans[i]), putc('\n');
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值