[ACNOI2022]数据结构之痛

137 篇文章 1 订阅
12 篇文章 0 订阅

题目

题目背景
《扬州慢·为卷爷吊打有感》

西师附中,联考之盟,解鞍静坐如松。过三时有半,得分数尚低。至揭榜大势已去,但见卷爷,独占榜一。渐黄昏,速补题毕,势惊游龙。

泥人有情,连日见虐起恨心。纵抱团相依,气血耗尽,卷爷亦赢。机房容颜未改,独缺予、无人送行。遇当年卷爷,已是今日国集!

题目描述
f ( b 1 , b 2 , … , b m ) = ∑ i = 1 m − 1 ω ( ∑ j = 1 i b j ) f(b_1,b_2,\dots,b_m)=\sum_{i=1}^{m-1}\omega\left(\sum_{j=1}^{i}b_j\right) f(b1,b2,,bm)=i=1m1ω(j=1ibj),其中 ω ( x ) = min ⁡ { x ,    k − x }    ( 0 ⩽ x < k ) \omega(x)=\min\{x,\;k{-}x\}\;(0\leqslant x<k) ω(x)=min{x,kx}(0x<k) ω ( x ) = ω ( x   m o d   k ) \omega(x)=\omega(x\bmod k) ω(x)=ω(xmodk) 。但是,若 ∑ j = 1 m b j \sum_{j=1}^{m}b_j j=1mbj 不是 k k k 的倍数,则定义 f = − 1 f=-1 f=1

现在给出序列 { a } \{a\} {a},求 ∑ i = 1 n ∑ j = i n f ( a i , a i + 1 , … , a j ) \sum_{i=1}^{n}\sum_{j=i}^{n}f(a_i,a_{i+1},\dots,a_j) i=1nj=inf(ai,ai+1,,aj),答案对 998244353 998244353 998244353 取模。

数据范围与约定
n ⩽ 1 0 6 n\leqslant 10^6 n106 k ⩽ 1 0 8 k\leqslant 10^{8} k108

思路

也许我的思路过分局限了。我首先就考虑(猫树)分治。不难发现当区间 [ l , r ] [l,r] [l,r] 和为 k k k 的倍数时, ω \omega ω 值等于 [ l , m ) [l,m) [l,m) 的前缀计算(包括整个区间)和 [ m , r ] [m,r] [m,r] 的后缀计算(不包括整个区间)。因为总和为 k k k 时,前缀的 ω \omega ω 值等于其补集(一个后缀)的 ω \omega ω 值。

但是,想要算出来 “前缀值” 需要 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn),总复杂度 O ( n log ⁡ 2 n ) \mathcal O(n\log^2n) O(nlog2n),无法通过。

又考虑固定右端点。我本以为,多个左端点对应的 ω \omega ω 的变化比较杂乱,难以搞定;我们永远滴神 R a i n y b u n n y \sf{Rainybunny} Rainybunny 却告诉我,其实就是个 线性变换,可以用矩阵维护的。维护三元向量 ( w , r , 1 ) (w,r,1) (w,r,1),其中 w w w 对应当前 ω \omega ω r r r 为区间和模 k k k 的结果。那么,右端点移动,等价于将 r r r 进行整体加,然后将 r r r 超过 k k k 的部分减去 k k k 。可以用数据结构维护,比如 t r e a p \tt treap treap 。对于 w w w 的变化,要么是 + r +r +r,要么是 + k − r +k{-}r +kr,都可以写成矩阵。所以其实可以直接 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

不幸的是,数据结构题不能忽略常数。据称,平衡树的常数 ≫ \gg 线段树 > > > 树状数组。又乘了矩阵的大常数,所以 R a i n y b u n n y \sf{Rainybunny} Rainybunny 没能通过本题,他能够因此长一智,我打心底里感到欣慰

我还是太呆了。有用的 f [ l , r ] f[l,r] f[l,r] 肯定满足前缀和 s r = s l − 1 s_r=s_{l-1} sr=sl1 。不难发现,若 [ l , m ) [l,m) [l,m) 是合法的区间, [ m , r ) [m,r) [m,r) 是合法区间,则 [ l , r ) [l,r) [l,r) 合法且 f [ l , r ) = f [ l , m ) + f [ m , r ) f[l,r)=f[l,m)+f[m,r) f[l,r)=f[l,m)+f[m,r) 。跟前面的分治做法用到相同的结论。

所以我们只需要对 “本原” 合法串计算 f f f 值,乘上系数即可。只有 O ( n ) \mathcal O(n) O(n) 个询问,可以离线后用芬威克树解决。记 s i s_i si 为前缀和数组,则 f [ l , r ] = ∑ i = l r ω ( s i − s l − 1 ) f[l,r]=\sum_{i=l}^{r}\omega(s_i-s_{l-1}) f[l,r]=i=lrω(sisl1) 。只需求 ∑ i = 1 κ ω ( s i − λ ) \sum_{i=1}^{\kappa}\omega(s_i-\lambda) i=1κω(siλ) 然后作差。用一个权值线段树(事实上是树状数组)记录前缀 { s i } \{s_i\} {si} 的情况,查询就分段查 s i ∈ [ λ , λ + ⌊ k 2 ⌋ ] s_i\in[\lambda,\lambda+\lfloor{k\over 2}\rfloor] si[λ,λ+2k⌋] 和剩余部分。时间复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn),常数不大。

代码

#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG LONG for loneliness)
#include <cctype> // DDG yydDOG & ZXY yydBUS !!!
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MOD = 998244353;
inline void modAddUp(int &x, const int &y){
	if((x += y) >= MOD) x -= MOD;
}

const int MAXN = 1000006;
int cnt[MAXN], sum[MAXN], cnt_all, sum_all, n, k, v[MAXN];
int query(int l, int r){
	if(l <= r){ // a complete range
		llong res = llong(v[l]+k)*cnt_all-sum_all; // rt+k-i
		for(int i=r; i; i&=(i-1)) // i-rt
			res = (res-llong(k+(v[l]<<1))*cnt[i]+(sum[i]<<1))%MOD;
		for(int i=l-1; i; i&=(i-1)) // rt-i
			res = (res-(sum[i]<<1)+llong(v[l]<<1)*cnt[i])%MOD;
		return int(res < 0 ? res+MOD : res);
	}
	else{ // two ranges
		llong res = sum_all-llong(cnt_all)*v[l]; // i-rt
		for(int i=l-1; i; i&=(i-1)) // rt-i
			res = (res-(sum[i]<<1)+llong(v[l]<<1)*cnt[i])%MOD;
		for(int i=r; i; i&=(i-1)) // i+k-rt
			res = (res+(sum[i]<<1)+llong(k-(v[l]<<1))*cnt[i])%MOD;
		return int(res < 0 ? res+MOD : res);
	}
}

int s[MAXN], lst[MAXN], nxt[MAXN];
int lcnt[MAXN], rcnt[MAXN], ans[MAXN];
int main(){
	n = readint(), k = readint();
	rep(i,1,n) if((s[i] = readint()+s[i-1]) >= k) s[i] -= k;
	memcpy(v+1,s,(n+1)<<2), std::sort(v+1,v+n+2);
	int *_end = std::unique(v+1,v+n+2);
	memset(lst+1,-1,(n+1)<<2); // clear
	for(int i=0,r; i<=n; ++i){
		s[i] = int(std::lower_bound(v+1,_end,s[i])-v);
		for(int j=s[i]; j<=n+1; j+=(j&-j)) // modify
			++ cnt[j], modAddUp(sum[j],v[s[i]]);
		++ cnt_all, modAddUp(sum_all,v[s[i]]);

		const int want = v[s[i]]+(k>>1);
		if(want < k || want-k < v[1])
			r = int(std::lower_bound(
				v+1,_end,want+1)-v-1);
		else r = int(std::lower_bound(
			v+1,_end,want-k+1)-v-1);
		ans[i] = MOD-query(s[i],r); // fast

		if(lst[s[i]] != -1){
			const int &pre = lst[s[i]];
			modAddUp(ans[pre],query(s[pre],r));
			nxt[pre] = i, lcnt[i] = lcnt[pre]+1;
		}
		else lcnt[i] = 1; // start
		lst[s[i]] = i; // new position
	}
	int xjx = 0; ///< the final answer
	drep(i,n,0) // everything
		if(!nxt[i]) rcnt[i] = 1;
		else{ // calculate
			rcnt[i] = rcnt[nxt[i]]+1;
			xjx = int((xjx+llong(rcnt[nxt[i]])
				*lcnt[i]%MOD*ans[i])%MOD);
		}
	memset(rcnt+1,0,(n+1)<<2); // reuse
	llong bad = llong(n+1)*n>>1;
	rep(i,0,n) ++ rcnt[s[i]]; // bucket sort
	rep(i,1,n+1) bad -= llong(rcnt[i]-1)*rcnt[i]>>1;
	xjx = int((xjx-bad%MOD+MOD)%MOD);
	printf("%d\n",xjx);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值