CF1553(Div.1+Div.2) F 题解

这是一篇豪夺洛谷最优解榜倒数 rnk 1 的题解。所以我为啥还有脸写

Description

给定序列 a a a

t i = ∑ j = 1 i ∑ k = 1 i a j  mod  a k t_i=\sum_{j=1}^i \sum_{k=1}^i a_j \ \text{mod} \ a_k ti=j=1ik=1iaj mod ak

你需要分别求出 t 1 , t 2 , ⋯   , t n t_1,t_2,\cdots,t_n t1,t2,,tn

1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ a i ≤ 3 × 1 0 5 1 \le n \le 2 \times 10^5,1 \le a_i \le 3 \times 10^5 1n2×105,1ai3×105

Solution

算法一


f i = ∑ j < i ( a i m o d    a j ) f_i=\sum_{j<i} (a_i \mod a_j) fi=j<i(aimodaj)

g i = ∑ j < i ( a j m o d    a i ) g_i=\sum_{j<i} (a_j \mod a_i) gi=j<i(ajmodai)

则第 i i i 个答案为

∑ j = 1 i f j + ∑ j = 1 i g j \sum_{j=1}^i f_j+\sum_{j=1}^i g_j j=1ifj+j=1igj

关键在于求出每一个 f i , g i f_i,g_i fi,gi

Part 1: 求 f

根据 x m o d    y = x − ⌊ x y ⌋ y x \mod y=x-\lfloor \frac x y \rfloor y xmody=xyxy,有

f i = a i ( i − 1 ) ∑ j < i ⌊ a i a j ⌋ a j f_i=a_i(i-1)\sum_{j<i} \left \lfloor \frac {a_i} {a_j} \right \rfloor a_j fi=ai(i1)j<iajaiaj

考虑枚举 j j j

对于每一个这样的 j j j,枚举值域区间 [ 0 , a j − 1 ] [ a j , 2 a j − 1 ] ⋯ [0,a_j-1][a_j,2a_j-1]\cdots [0,aj1][aj,2aj1],那么 j j j 会向所有满足 a i ∈ [ l , r ] a_i \in [l,r] ai[l,r] i > j i>j i>j f i f_i fi 产生贡献。

我们在 j + 1 j+1 j+1 处打上标记 ( r , 1 ) ( l − 1 , − 1 ) (r,1)(l-1,-1) (r,1)(l1,1),其中在 x x x 处的标记 ( p , △ ) (p,\triangle) (p,) 表示,对于所有 x x x 之后且权值不小于 p p p f i f_i fi 都要加上 △ \triangle

从左往右扫描,用树状数组维护即可。时间复杂度 O ( m ln ⁡ m log ⁡ n ) O(m \ln m \log n) O(mlnmlogn)。其中 m m m 表示所有 a i a_i ai 的最大值。

Part 2: 求 g

同理,有

g i = ∑ j < i a j − ∑ j < i ⌊ a j a i ⌋ a i g_i=\sum_{j<i} a_j-\sum_{j<i} \left \lfloor \frac {a_j} {a_i} \right \rfloor a_i gi=j<iajj<iaiajai

枚举 [ 0 , a i − 1 ] [ a i , 2 a i − 1 ] , ⋯ [0,a_i-1][a_i,2a_i-1],\cdots [0,ai1][ai,2ai1], 并查询 [ 1 , i ) [1,i) [1,i) 中有多少个在该区间中的数。也可以使用树状数组维护。


总复杂度 O ( n + m ln ⁡ m log ⁡ n ) O(n+m \ln m \log n) O(n+mlnmlogn),本题被解决。

算法二(加强版: a i a_i ai 可能相同)

考虑根号分治。

我们将数分为两类——第一类数不超过 B B B,称它们为 ⌈ \lceil 小数 ⌋ \rfloor ;第二类数超过 B B B,称它们为 ⌈ \lceil 大数 ⌋ \rfloor 。小数的性质是值域较小,大数的性质是倍数较少。

Part 1

先考虑小数。为了利用它们值域小的性质,我们开一个桶,其中第 i i i 个桶 V i V_i Vi 里面装了所有值为 i i i 的数的位置。枚举编号在 [ 1 , B ] [1,B] [1,B] 中的桶,并考虑计算出它们对序列 t t t 的贡献。

令当前需要计算贡献的桶是第 x ( x ≤ B ) x(x \le B) x(xB) 个。由于模运算需要两个数,所以还要再枚举一个桶,令这是第 y y y 个桶。显然, ∀ i ∈ V x , j ∈ V y \forall i \in V_x,j \in V_y iVx,jVy,它们都对 t [ max ⁡ ( i , j ) , n ] t[\max(i,j),n] t[max(i,j),n] x  mod  y x \ \text{mod} \ y x mod y 的贡献。

为方便进一步的推导,考虑将 max ⁡ ( i , j ) \max(i,j) max(i,j) max ⁡ \max max 去掉,即分类地钦定 i , j i,j i,j 之间的大小关系。我们先预处理出一个序列 C C C,其中第 k k k 位上的数是 V i V_i Vi 中不超过 k k k 的数的个数,那么对于某个属于 V y V_y Vy j j j,它作为 max ⁡ ( i , j ) \max(i,j) max(i,j) 的次数为 C j C_j Cj 次,其中 m m m 表示所有 a i a_i ai 的最大值,可以直接在 t t t 上差分。但是,若 j = min ⁡ ( i , j ) j=\min(i,j) j=min(i,j),那么应该在 i i i 处差分地修改,但 i i i 我们并不清楚。这该怎么办呢?

考虑维护另一个数组 D D D,其中 D i D_i Di 表示,当 i i i 作为 max ⁡ ( i , j ) \max(i,j) max(i,j)(即 j j j 作为 min ⁡ ( i , j ) \min(i,j) min(i,j))时的总贡献。这个数组可以采用下面的方法求出——对于每个属于其他桶的 j j j,在 D j D_j Dj 处差分地加上 i  mod  j i \ \text{mod} \ j i mod j 以及 j  mod  i j \ \text{mod} \ i j mod i。在枚举完毕后,将 D D D 求一遍前缀和得到真实的 D D D,再去逐一向 t t t 差分地贡献。

总结一下 ⌈ \lceil 小数 ⌋ \rfloor 部分的处理方式:

  • 建立一个桶;
  • 扫描桶 [ 1 , B ] [1,B] [1,B]
    • 通过前缀和技巧求出 C C C 数组;
    • 枚举桶 [ 1 , m ] [1,m] [1,m] 以及其中的数 j j j,分类讨论 j j j i , j i,j i,j 中的较大者以及较小者两种情况:
      • 作为较大者: 直接在 t t t 上差分地修改。
      • 作为较小者: 在 D D D 上差分地修改。
    • 做前缀和得到真实的 D D D,并在 t t t 上差分地修改,解决 i = max ⁡ ( i , j ) i=\max(i,j) i=max(i,j) 的情况。

此部分复杂度为 O ( B ( n + m ) ) O(B(n+m)) O(B(n+m))

Part 2

现在,我们已经处理完了,所有与小数有关的取模式,现在只需要考虑大数与大数之间产生的贡献。枚举一个大数,它有两种情况,一是向别人取模,一是被别人取模。这两种情况联系不大,需要进行分讨。

Part 2.1: 被别人取模

对于大数而言,其倍数较少;也就是说,当某个数 num \text{num} num 逐渐增大的时候,其对大数取模的值的极大连续段的数量较少。例如,这个大数是 1 0 4 10^4 104,那么当 num \text{num} num 1 1 1 缓慢增加到 1 0 5 10^5 105 的过程中,模数会从 0 0 0 变化到 1 0 4 − 1 10^4-1 1041,然后又回到 0 0 0 再变成 1 0 4 − 1 10^4-1 1041,而这样变化的轮数的只有 O ( m B ) O(\frac m B) O(Bm)

直接在原序列上扫描。

令当前看到的数是一个大数,且其值为 c c c。那么,我们需要查询 ∑ i = 0 c − 1 i × c n t i \sum_{i=0}^{c-1} i \times cnt_i i=0c1i×cnti ∑ i = c 2 c − 1 ( i − c ) × c n t i \sum_{i=c}^{2c-1} (i-c) \times cnt_i i=c2c1(ic)×cnti ⋯ ⋯ ⋯ ⋯ \cdots \cdots \cdots \cdots

其中 c n t i cnt_i cnti 表示目前 i i i 出现的次数。

于是,问题变为了一道纯粹的操作、查询类题目。

  • 给定一个长度为 n n n 的序列 c n t cnt cnt
  • 你需要支持单点修改,查询区间 i × c n t i i \times cnt_i i×cnti 的和,查询区间 c n t i cnt_i cnti 的和。

若直接采用树状数组维护,那么复杂度是 O ( n m B log ⁡ m ) O(n \frac {m} {B} \log m) O(nBmlogm),难以接受。

注意到,查询次数为 n × m B n \times \frac m {B} n×Bm 次,操作次数只有 n n n 次,进行根号平衡即可。复杂度为 O ( n m B ) O(n \frac m B) O(nBm)

Part 2.2: 向别人取模

先将 x m o d    y x \mod y xmody 变为 x − ⌊ x y ⌋ y x-\lfloor \frac x y \rfloor y xyxy。那么,等价于对于每个满足 a i > B a_i>B ai>B i i i,求出

a i × ( ∑ j = 1 i − 1 [ a j > B ] ) − ∑ j = 1 i − 1 [ a j > B ] ⌊ a i a j ⌋ a j a_i \times \left(\sum_{j=1}^{i-1} [a_j>B]\right)-\sum_{j=1}^{i-1} [a_j>B] \left \lfloor \frac {a_i} {a_j} \right \rfloor a_j ai×(j=1i1[aj>B])j=1i1[aj>B]ajaiaj

c n t j cnt_j cntj 表示,此前有多少个位置的 a a a j j j。同时维护一个 cnt_big \text{cnt\_big} cnt_big,表示此前大数的数量。那么,式子可以被化为

cnt_big × a i − ∑ j = 1 m c n t j ⌊ a i j ⌋ \text{cnt\_big} \times a_i-\sum_{j=1}^m cnt_j \left \lfloor \frac {a_i} {j} \right \rfloor cnt_big×aij=1mcntjjai

对于前半部分,直接计算。
对于后半部分,暴力整除分块,对于每一个区间(块) [ l , r ] [l,r] [l,r],查询区间内 c n t cnt cnt 和。

注意到,这是一个 n n n 次单点修改, n m n \sqrt m nm 次区间查询的问题,也可以通过根号平衡做到 O ( n m ) O(n \sqrt m) O(nm )


不妨设 n , m n,m n,m 同级,则总复杂度 O ( B n + n 2 B ) O(Bn+\frac {n^2} {B}) O(Bn+Bn2),取 B = n B=\sqrt n B=n 时最优, O ( n n ) O(n \sqrt n) O(nn ) 可以通过加强版。

Code

//根号分治做法
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define rg register
#define ll long long
using namespace std;
const int maxl=300005;

int read(){
	int s=0,w=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}
	while (ch>='0'&&ch<='9'){s=s*10+(ch^'0');ch=getchar();}
	return s*w;
}
int m,n,B,blo,pre_big;
int a[maxl],cnt[maxl],bl[maxl];
ll ans[maxl],cf[maxl],sum0[maxl],sum1[maxl],sum0_bl[maxl],sum1_bl[maxl];
vector<int> ve[maxl];

void change0(int rt,ll num){	
	int le=(bl[rt]-1)*blo+1,ri=rt;
	for (int i=le;i<=ri;++i)  sum0[i]+=num;
	
	le=1,ri=bl[rt]-1;
	for (int i=le;i<=ri;++i)  sum0_bl[i]+=num;
}

void change1(int rt,ll num){
	int le=(bl[rt]-1)*blo+1,ri=rt;
	for (int i=le;i<=ri;++i)  sum1[i]+=num;
	
	le=1,ri=bl[rt]-1;
	for (int i=le;i<=ri;++i)  sum1_bl[i]+=num;
}
ll Query0(int l,int r){return sum0[l]+sum0_bl[bl[l]]-sum0[r+1]-sum0_bl[bl[r+1]];}
ll Query1(int l,int r){return sum1[l]+sum1_bl[bl[l]]-sum1[r+1]-sum1_bl[bl[r+1]];} 

signed main(){
	n=read();
	for (int i=1;i<=n;i++){
		a[i]=read();
		m=max(m,a[i]);
		ve[a[i]].push_back(i);
	}
	B=sqrt(m)/4,blo=sqrt(m);
	for (int i=1;i<=m+1;i++)  bl[i]=(i-1)/blo+1;
	for (int i=1;i<=B;i++){
		if (!ve[i].size())  continue;
		
		for (int j=0;j<(int)ve[i].size();j++)  cnt[ve[i][j]]++;
		for (int j=1;j<=m;j++)  cnt[j]=cnt[j-1]+cnt[j];
		for (rg int j=1;j<=B;++j){
			if (i==j)  continue;
			
			int tmp=(i%j)+(j%i);
			for (int k=0;k<(int)ve[j].size();++k){
				int now=ve[j][k];
				cf[now]+=tmp;
			}
		}
		for (rg int j=B+1;j<=m;++j){
			int tmp=(i%j)+(j%i);
			for (int k=0;k<(int)ve[j].size();++k){
				int now=ve[j][k];
				ans[now]+=(ll)cnt[now]*tmp,cf[now]+=tmp;
			}
		}
		for (int j=1;j<=m;++j)  cf[j]=cf[j-1]+cf[j];
		for (rg int j=0;j<(int)ve[i].size();++j)  ans[ve[i][j]]+=cf[ve[i][j]];
		memset(cf,0,sizeof(cf));
		memset(cnt,0,sizeof(cnt));
	}
	for (int i=1;i<=n;i++){
		if (a[i]<=B)  continue;
		
		ll cur=0;
		for (int j=0;j<=m;j+=a[i]){
			int l=max(j,1),r=min(j+a[i]-1,m);
			
			cur+=Query1(l,r);
			if (j)  cur-=(ll)Query0(l,r)*j;
		}
		change0(a[i],1),change1(a[i],a[i]),ans[i]+=cur;
	}
	for (int i=1;i<=m+1;i++)  sum0[i]=sum1[i]=sum0_bl[i]=sum1_bl[i]=0;
	for (int i=1;i<=n;i++){
		if (a[i]<=B)  continue;
		
		ll now=(ll)a[i]*pre_big,res=0;
		for (int l=1,r;l<=a[i];l++){
			r=a[i]/(a[i]/l);
			res+=(ll)Query0(l,r)*(a[i]/l);
			l=r;
		}
		ans[i]+=now-res,pre_big++,change0(a[i],a[i]);
	}
	for (int i=1;i<=n;i++)  ans[i]+=ans[i-1];
	for (int i=1;i<=n;i++)  printf("%lld ",ans[i]);
	puts("");
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值