P4062 [Code+#1]Yazid 的新生舞会 树状数组维护三阶前缀和

本文介绍了如何利用偏序性质和等差序列特性,通过树状数组高效维护三阶前缀和来解决Yazid的新生舞会问题,通过区间加法和查询操作降低时间复杂度至O(nlogn)。关键步骤包括统计满足特定偏序关系的对数、等差序列插入与查询策略,以及维护bit0、bit1和bit2树状数组来优化计算。
摘要由CSDN通过智能技术生成

P4062 [Code+#1]Yazid 的新生舞会 树状数组维护三阶前缀和

神仙树状数组\线段树题

P4062 [Code+#1]Yazid 的新生舞会

发现偏序性质

我们希望统计“新生舞会”子区间,先要发现它的性质。

从简单的情况入手,例如考虑只有 0 和 1 的序列,如果统计 1 的“新生舞会”子区间,那么条件是 c n t ( l , r ) > ⌊ r − l + 1 2 ⌋ cnt(l,r)>\lfloor\dfrac{r-l+1}{2}\rfloor cnt(l,r)>2rl+1 . 自然地,我们想到维护一个前缀和快速求得 c n t ( l , r ) cnt(l,r) cnt(l,r) ,记 S i S_i Si 表示 [ 1 , i ] [1,i] [1,i] 中 1 的个数,那么上式改写为:
S r − S l − 1 > r − l + 1 2 2 S r − r > 2 S l − 1 − ( l − 1 ) \begin{aligned} S_r-S_{l-1}&>\dfrac{r-l+1}{2}\\[2ex] 2S_r-r &> 2S_{l-1}-(l-1) \end{aligned} SrSl12Srr>2rl+1>2Sl1(l1)
于是我们发现,只要统计满足以上偏序关系的偏序对的个数就能得到 1 的“新生舞会”子区间个数。

利用等差序列性质改进算法

但是如果对每一个数都做一遍统计,不难发现其复杂度是 O ( n 2 ) O(n^2) O(n2) 的,我们需要改进复杂度。

继续从简单情况入手,我们观察 2 S i − i 2S_i-i 2Sii 的性质:

i i i12345678
a i a_i ai00101100
2 S i − i 2S_i-i 2Sii-1-2-1-2-10-1-2

我们可以发现一个重要性质: 2 S i − i 2S_i-i 2Sii 总是以 a i = 1 a_i=1 ai=1 的位置为开头或者结尾组成一段公差为 − 1 -1 1 的等差序列。

可以发现 [ 1 , 2 ] [1,2] [1,2] [ 3 , 4 ] [3,4] [3,4] [ 5 , 5 ] [5,5] [5,5] [ 6 , 8 ] [6,8] [6,8] 2 S i − i 2S_i-i 2Sii 都组成了等差序列,那么如何利用这个性质?首先一个显然的结论是等差序列内部不会对答案产生贡献,因为内部总是递减,一定不满足之前推出的偏序关系,所以我们如果考虑插入等差序列(每一个数都作为一段等差序列的开头或者结尾 O ( n ) O(n) O(n)) 并查询等差序列与前面的等差序列产生的贡献(期望 O ( n log ⁡ n ) O(n\log n) O(nlogn)),就可能解决此题。

思考插入等差序列和查询答案的实现细节

现在做一个小推广,假设统计以下序列中,5 的“新生舞会”子区间

i i i12345678
a i a_i ai12535542

显然将 5 看作 1,其它数看作 0 就是上一个表格的情况。插入一段由 a i a_i ai [ 3 , 4 ] [3,4] [3,4] 对应的 2 S i − i 2S_i-i 2Sii 组成的等差序列 等价于给 2 S i − i 2S_i-i 2Sii 的值域区间 [ − 1 , − 2 ] [-1,-2] [1,2] 加上权值 1 ,也就是区间加。查询等差序列 [ 3 , 4 ] [3,4] [3,4] 与前面产生的贡献就是:求等差序列中,每一个数与前面满足偏序关系的数的个数之和。例如这个例子,就是统计前面与 − 1 -1 1 满足偏序关系的 2 S i − i 2S_i-i 2Sii 的个数加上前面与 − 2 -2 2 满足偏序关系的 2 S i − i 2S_i-i 2Sii 的个数。

那么用简洁的表达式表达,就是:

v ( x ) v(x) v(x) 2 S i − i = x 2S_i-i = x 2Sii=x i i i 个数,也就是 x x x 的权值;令 p = 2 S l − l p=2S_l-l p=2Sll 为等差序列开头的那个数 ,令 q = 2 S r − r q=2S_r-r q=2Srr 为等差序列结尾的那个数,那么由 [ l , r ] [l,r] [l,r] 中的 $ 2S_i-i$ 组成的等差序列与前面的等差序列产生的贡献就是:
∑ q ⩽ k ⩽ p ∑ t < k v ( t ) \sum_{q\leqslant k\leqslant p}\sum_{t < k} v(t) qkpt<kv(t)
为了提高运算速度,考虑用前缀和优化,并用树状数组维护前缀和。记 g ( x ) = ∑ k ⩽ x v ( k ) g(x)=\sum_{k\leqslant x} v(k) g(x)=kxv(k) ,是 2 S i − i ⩽ x 2S_i-i\leqslant x 2Siix i i i 的个数,也就是 v ( x ) v(x) v(x) 的前缀和;记 h ( x ) = ∑ k ⩽ x g ( k ) h(x) = \sum_{k\leqslant x} g(k) h(x)=kxg(k) ,表示对于所有 2 S i − i ⩽ x 2S_i-i\leqslant x 2Siix 2 S i − i 2S_i-i 2Sii ,满足 2 S j − j ⩽ 2 S i − i 2S_j-j\leqslant 2S_i-i 2Sjj2Sii j j j 的个数之和,也就是 g ( x ) g(x) g(x) 的前缀和;用上面的例子,我们知道查询 [ 3 , 4 ] [3,4] [3,4] 组成的等差序列与前面的贡献就是 h ( ( − 1 ) − 1 ) − h ( ( − 2 ) − 2 ) h((-1)-1)-h((-2)-2) h((1)1)h((2)2) 。普遍意义下,查询开头为 p = 2 S l − l p=2S_l-l p=2Sll ,结尾为 q = 2 S r − r q=2S_r-r q=2Srr 的等差序列与前面的贡献是 h ( p − 1 ) − h ( q − 2 ) h(p-1)-h(q-2) h(p1)h(q2) ,这样就能求得答案。

于是我们可以制作下表:

x = S i − i x=S_i-i x=Sii-3-2-101
v ( x ) v(x) v(x)01100
g ( x ) g(x) g(x)01222
h ( x ) h(x) h(x)01357

然后考虑加入一段等差序列的问题:上面也已经说了,插入一段以 [ l , r ] [l,r] [l,r] 中的 $ 2S_i-i$ 组成的等差序列等价于在 2 S i − i 2S_i-i 2Sii 的权值域 v v v 中对区间 [ 2 S r − r , 2 S l − l ] [2S_r-r,2S_l-l] [2Srr,2Sll] 区间加 1.于是我们考虑对 v v v 区间加 1 后,对 h h h 有什么影响就好了,这里的思考思路参考《挑战程序设计竞赛》中处理树状数组区间加和区间查询的思路。

树状数组维护三阶前缀和

现在我们将目光转向 v , g , h v,g,h v,g,h 三个函数,以下的 [ l , r ] [l,r] [l,r] 都是指三个函数的定义域 x = S i − i x = S_i-i x=Sii 上的区间(前面的是指 a i a_i ai

考虑令满足 l ⩽ x ⩽ r l\leqslant x\leqslant r lxr v ( x ) v(x) v(x) 全部加 1.

记操作前的 x x x 对应的 g g g 函数为 g ( x ) g(x) g(x) ,操作后为 g ′ ( x ) g'(x) g(x) h h h 函数同理。分类讨论:

  • x < l x<l x<l ,则 g ( x ) g(x) g(x) h ( x ) h(x) h(x) 不受影响,即 g ′ ( x ) = g ( x ) , h ′ ( x ) = h ( x ) g'(x)=g(x),h'(x)=h(x) g(x)=g(x),h(x)=h(x)

  • l ⩽ x ⩽ r l\leqslant x\leqslant r lxr ,则
    g ′ ( x ) = g ( x ) + x − l + 1 h ′ ( x ) = h ( x ) + ∑ k = l x ( k − l + 1 ) = h ( x ) + ( x − l + 1 ) ( x − l + 2 ) 2 = h ( x ) + 1 2 ( x 2 + ( 3 − 2 l ) x + l 2 − 3 l + 2 ) \begin{aligned} g'(x) &= g(x)+x-l+1\\[2ex] h'(x) &= h(x)+\sum_{k = l}^{x} (k-l+1)\\[1ex] &=h(x)+\dfrac{(x-l+1)(x-l+2)}{2}\\[1ex] &= h(x) + \dfrac{1}{2}\left(x^2+(3-2l)x+l^2-3l+2\right) \end{aligned} g(x)h(x)=g(x)+xl+1=h(x)+k=lx(kl+1)=h(x)+2(xl+1)(xl+2)=h(x)+21(x2+(32l)x+l23l+2)
    可以借用上面给出的表格与例子推一推。

  • r < x r<x r<x ,则
    g ′ ( x ) = g ( x ) + r − l + 1 h ′ ( x ) = h ( x ) + ∑ k = l r ( k − l + 1 ) + ( x − r ) ( r − l + 1 ) = h ( x ) + ( r − l + 2 ) ( r − l + 1 ) 2 + ( x − r ) ( r − l + 1 ) = h ( x ) + 1 2 ( 2 ( r − l + 1 ) x + ( r − l + 2 ) ( r − l + 1 ) − 2 r ( r − l + 1 ) ) \begin{aligned} g'(x) &= g(x) + r-l+1\\[2ex] h'(x)&=h(x) + \sum_{k=l}^r(k-l+1)+(x-r)(r-l+1)\\[1ex] &=h(x) + \dfrac{(r-l+2)(r-l+1)}{2}+(x-r)(r-l+1)\\[1ex] &= h(x) + \dfrac{1}{2}\left(2(r-l+1)x+(r-l+2)(r-l+1)-2r(r-l+1)\right) \end{aligned} g(x)h(x)=g(x)+rl+1=h(x)+k=lr(kl+1)+(xr)(rl+1)=h(x)+2(rl+2)(rl+1)+(xr)(rl+1)=h(x)+21(2(rl+1)x+(rl+2)(rl+1)2r(rl+1))

然后我们发现,变化量与 x 2 x^2 x2 x x x 和常数项( x 0 x^0 x0)有关,于是用三个树状数组 b i t 0 \rm{bit0} bit0 b i t 1 \rm{bit1} bit1 b i t 2 \rm{bit2} bit2 分别维护这三项,具体做法是:

对于 [ l , r ] [l,r] [l,r] 区间加 1:

  • b i t 0 ( l ) {\rm{bit0}}(l) bit0(l) 加上 l 2 − 3 l + 2 l^2-3l+2 l23l+2;令 b i t 1 ( l ) {\rm bit1}(l) bit1(l) 加上 3 − 2 l 3-2l 32l ;令 b i t 2 ( l ) {\rm bit2}(l) bit2(l) 加上 1 1 1
  • b i t 0 ( r + 1 ) {\rm bit0}(r + 1) bit0(r+1) 加上 ( r − l + 2 ) ( r − l + 1 ) − 2 r ( r − l + 1 ) − ( l 2 − 3 l + 2 ) (r-l+2)(r-l+1)-2r(r-l+1)-(l^2-3l+2) (rl+2)(rl+1)2r(rl+1)(l23l+2) ;令 b i t 1 ( r + 1 ) {\rm bit1(r + 1)} bit1(r+1) 加上 2 ( r − l + 1 ) − ( 3 − 2 l ) 2(r-l+1)-(3-2l) 2(rl+1)(32l) ;令 b i t 2 ( r + 1 ) {\rm bit2}(r + 1) bit2(r+1) 加上 − 1 -1 1

查询的时候,例如查 h ( x ) h(x) h(x) 就是
h ( x ) = Query ⁡ ( b i t 0 , x ) + Query ⁡ ( b i t 1 , x ) × x + Query ⁡ ( b i t 2 , x ) × x 2 2 h(x)=\dfrac{\operatorname{Query}({\rm bit0},x)+\operatorname{Query}({\rm bit1},x)\times x+\operatorname{Query}({\rm bit2},x)\times x^2}{2} h(x)=2Query(bit0,x)+Query(bit1,x)×x+Query(bit2,x)×x2
这里乘上 1 2 \dfrac{1}{2} 21 是因为我们刚刚修改的时候没有乘上 1 2 \dfrac{1}{2} 21 .

以上式子还要慢慢理解。这里再放出一张图,用于理解二阶前缀和的变化,也就是 g ( x ) g(x) g(x) 的变化,至于 h ( x ) h(x) h(x) 的变化,确实需要一定的数学功底与思维能力理解(这几条式子我也推了很久,推了 2h ,而且有几次推错了,甚至有几次多项式相乘展开都算错了,我数学太菜了…)

在这里插入图片描述

整理思路,解决问题

综上所述,我们就可以将记录一种数出现的所有位置,然后将原序列简化为仅包含 0 和 1 的序列,维护一下 h ( x ) h(x) h(x) 并统计答案,逐个击破即可。最后要注意的就是不要傻傻地将负数作为下标了,设置一个偏移量 Δ \Delta Δ 就可以避免数组越界地情况。时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

参考代码
#include<iostream>
#include<cstdio>
#include<vector> 

using namespace std;
typedef long long ll;
const ll dlt = 500003;
ll ans,t[5][1000010];
int n,type,x;
vector<ll > a[500005];

int lowbit(int num)
{
	return num&(-num);
}

void bitadd(int ty,int i,ll val)
{
	for(;i <= n + dlt;i += lowbit(i)) t[ty][i] += val;
	return ;
}

ll bitq(int ty,int i)
{
	ll res = 0;
	for(;i;i -= lowbit(i)) res += t[ty][i];
	return res;
}

ll BIT_Query(ll num)
{
	ll res = 0;
	res = ((bitq(0,num) + bitq(1,num)*num + bitq(2,num)*num*num)/2);
	return res;
}

void BIT_Add(ll l,ll r,ll val)//维护三个树状数组
{
	ll tmp0 = l*l - (ll)3*l + (ll)2;
	ll tmp1 = (ll)3 - l - l;
	ll tmp2 = (ll)1;
	bitadd(0,l,tmp0*val);
	bitadd(1,l,tmp1*val);
	bitadd(2,l,tmp2*val);
	tmp0 = -tmp0 + (r - l + (ll)1)*(r - l + (ll)2) - (ll)2*r*(r - l + (ll)1) ;
	tmp1 = -tmp1 + (ll)2*(r - l + (ll)1);
	tmp2 = -tmp2;
	bitadd(0,r + 1,tmp0*val);
	bitadd(1,r + 1,tmp1*val);
	bitadd(2,r + 1,tmp2*val);
	return ;
}

void solve(ll num)
{
	ll cnt = 0;ll lt = 0;ll rt = 0;
	BIT_Add(dlt,dlt,1);\\dlt 是偏移量
	if(a[num][0] > 1) lt = -a[num][0] + 1 ,rt = -1,BIT_Add(lt + dlt,rt + dlt,1); 
	for(int i = 0;i < a[num].size() - 1;i ++)//插入,查询,统计答案
	{
		cnt ++;rt = cnt + cnt - a[num][i];lt = cnt + cnt - a[num][i + 1] + 1;
		ans += BIT_Query(rt - 1 + dlt) - BIT_Query(lt - 2 + dlt);
		BIT_Add(lt + dlt,rt + dlt,1);
	}
	cnt = 0;
	BIT_Add(dlt,dlt,-1);
	if(a[num][0] > 1) lt = - a[num][0] + 1,rt = -1,BIT_Add(lt + dlt,rt + dlt,-1);
	for(int i = 0;i < a[num].size() - 1;i ++)//清空树状数组
	{
		cnt ++;lt = cnt + cnt - a[num][i + 1] + 1;rt = cnt + cnt - a[num][i];
		BIT_Add(lt + dlt,rt + dlt,-1);
	}
	return ;
}

int main()
{
	scanf("%d%d",&n,&type);
	for(int i = 1;i <= n;i ++)
	{
		scanf("%d",&x);
		a[x].push_back(i);//记录位置
	}
	for(int i = 0;i <= n;i ++)//逐个击破
		if(!a[i].empty())
		{
			a[i].push_back(n + 1);
			solve(i);
		}
	printf("%lld",ans);
	return 0;
 } 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值