题目
题目背景 《扬州慢·为卷爷吊打有感》
西师附中,联考之盟,解鞍静坐如松。过三时有半,得分数尚低。至揭榜大势已去,但见卷爷,独占榜一。渐黄昏,速补题毕,势惊游龙。
泥人有情,连日见虐起恨心。纵抱团相依,气血耗尽,卷爷亦赢。机房容颜未改,独缺予、无人送行。遇当年卷爷,已是今日国集!
题目描述 记
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=1m?1?ω(∑j=1i?bj?),其中
ω
(
x
)
=
min
?
{
x
,
??
k
?
x
}
??
(
0
?
x
<
k
)
\omega(x)=\min\{x,\;k{-}x\}\;(0\leqslant x<k)
ω(x)=min{x,k?x}(0?x<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=1m?bj? 不是
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=1n?∑j=in?f(ai?,ai+1?,…,aj?),答案对
998244353
998244353
998244353 取模。
数据范围与约定
n
?
1
0
6
n\leqslant 10^6
n?106 而
k
?
1
0
8
k\leqslant 10^{8}
k?108 。
思路
也许我的思路过分局限了。我首先就考虑(猫树)分治。不难发现当区间
[
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
+k?r,都可以写成矩阵。所以其实可以直接
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?=sl?1? 。不难发现,若
[
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?ω(si??sl?1?) 。只需求
∑
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>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
# 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){
llong res = llong(v[l]+k)*cnt_all-sum_all;
for(int i=r; i; i&=(i-1))
res = (res-llong(k+(v[l]<<1))*cnt[i]+(sum[i]<<1))%MOD;
for(int i=l-1; i; i&=(i-1))
res = (res-(sum[i]<<1)+llong(v[l]<<1)*cnt[i])%MOD;
return int(res < 0 ? res+MOD : res);
}
else{
llong res = sum_all-llong(cnt_all)*v[l];
for(int i=l-1; i; i&=(i-1))
res = (res-(sum[i]<<1)+llong(v[l]<<1)*cnt[i])%MOD;
for(int i=r; i; i&=(i-1))
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);
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))
++ 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);
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;
lst[s[i]] = i;
}
int xjx = 0;
drep(i,n,0)
if(!nxt[i]) rcnt[i] = 1;
else{
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);
llong bad = llong(n+1)*n>>1;
rep(i,0,n) ++ rcnt[s[i]];
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;
}
|