题目大意
有
n
n
n 个元素,第
i
i
i 个元素在初始
0
0
0 时刻时值为
a
i
a_i
ai,此后每个时刻增加
b
i
b_i
bi 并模
p
i
p_i
pi,即在
t
t
t 时刻时值为
(
a
i
+
b
i
⋅
t
)
mod
p
i
(a_i+b_i\cdot t)~\text{mod}~p_i
(ai+bi⋅t) mod pi,其中
t
t
t 为整数。
求
max
t
=
0
T
{
∑
i
=
1
n
(
a
i
+
b
i
⋅
t
)
mod
p
i
}
\max_{t=0}^T \bigg\{\sum_{i=1}^n (a_i+b_i\cdot t)~\text{mod}~p_i \bigg\}
t=0maxT{i=1∑n(ai+bi⋅t) mod pi}
输出这个最大值,及其对应的最早的时刻。
1
≤
n
,
T
≤
1
0
5
,
0
≤
a
i
,
b
i
<
p
i
,
5
×
1
0
8
<
p
i
<
1
0
9
1 \leq n,T \leq 10^5,~~0 \leq a_i,b_i < p_i,~~5\times10^8 < p_i < 10^9
1≤n,T≤105, 0≤ai,bi<pi, 5×108<pi<109
多测,
∑
n
≤
1
0
6
\sum n \leq 10^6
∑n≤106,80% 数据保证
n
≤
1000
n \leq 1000
n≤1000
保证
p
i
p_i
pi 为质数;
a
i
,
b
i
a_i,b_i
ai,bi 在
[
0
,
p
i
)
[0,p_i)
[0,pi) 范围内随机生成;
5s
\\
\\
\\
题解
鉴于目前这题只有两篇不讲人话的题解(官方题解和 Zayin 的博客),我决定补掉这题并写一篇世人能看得懂的题解
首先思考一些比较暴力的做法,一开始令
a
n
s
=
∑
i
=
1
n
a
i
ans=\sum_{i=1}^n a_i
ans=∑i=1nai,然后每往后走一个时刻默认给
a
n
s
ans
ans 加上
∑
i
=
1
n
b
i
\sum_{i=1}^n b_i
∑i=1nbi,然后我们对于每个
i
i
i 事先标记出哪些时刻要让
a
n
s
−
=
p
i
ans-=p_i
ans−=pi,这样就可以算出答案了。
这样乍一看是
O
(
n
T
)
O(nT)
O(nT) 的,但写准确一点应该是
O
(
n
⌊
b
i
T
p
i
⌋
)
O(n\lfloor\frac{b_iT}{p_i}\rfloor)
O(n⌊pibiT⌋) 的,因为
−
p
i
-p_i
−pi 的标记只有
⌊
a
i
+
b
i
T
p
i
⌋
\lfloor\frac{a_i+b_iT}{p_i}\rfloor
⌊piai+biT⌋ 个,因此单个
i
i
i 的预处理是
O
(
⌊
b
i
T
p
i
⌋
)
O(\lfloor\frac{b_iT}{p_i}\rfloor)
O(⌊pibiT⌋) 的。
这在
b
i
b_i
bi 小的时候很好,在
b
i
b_i
bi 大的时候(接近
p
i
p_i
pi 的时候)就变成
O
(
n
T
)
O(nT)
O(nT) 了。
所以接下来要用一些姿势,使得 b i b_i bi 能够变小。
举个例子观察一下,比如
b
i
=
4
,
p
i
=
11
b_i=4,~p_i=11
bi=4, pi=11,那么
(
b
i
⋅
t
)
mod
p
i
(b_i\cdot t)~\text{mod}~p_i
(bi⋅t) mod pi 会长这个样子:
4
,
8
,
1
,
5
,
9
,
2
,
6
,
10
,
3
,
7
,
0
,
4
,
⋯
4,8,1,5,9,2,6,10,3,7,0,4,\cdots
4,8,1,5,9,2,6,10,3,7,0,4,⋯
记
G
=
⌊
n
⌋
G=\lfloor \sqrt n \rfloor
G=⌊n⌋,比如我们假设
G
=
4
G=4
G=4,那么观察前
4
4
4 个数,记
s
t
p
stp
stp 表示这前
G
G
G 个数的最小值,
g
g
g 表示最小值在第几个取到。(这个例子中
s
t
p
=
1
,
g
=
3
stp=1,~g=3
stp=1, g=3)
把时间
t
t
t 按模
g
g
g 分组,则可以发现,这个数列分成了
g
g
g 组,每组起始值不同,间隔都是
s
t
p
stp
stp,并且
s
t
p
stp
stp 比较小。
s t p stp stp 有多小呢?在 b i b_i bi 随机的情况下是 O ( p i G ) O(\frac{p_i}{G}) O(Gpi) 的。这里引用一下 Zayin 的粗略证明:
粗略证明(其实严格的我也不会…)
当 a a a 很大(比如 a > p k a>\frac pk a>kp)时, a , 2 a % p , 3 a % p ⋯ k a % p a,2a\%p,3a\%p\cdots ka\%p a,2a%p,3a%p⋯ka%p 这个数列几乎可以认为是随机的,而在 [ 0 , p ) [0,p) [0,p) 中随机选 k k k 个数的最小值的期望是 P k + 1 \frac P{k+1} k+1P
当 a a a 很小(比如 a < p k a<\frac pk a<kp)时, a , 2 a % p , 3 a % p ⋯ k a % p a,2a\%p,3a\%p\cdots ka\%p a,2a%p,3a%p⋯ka%p 等价于 a , 2 a , 3 a ⋯ k a a,2a,3a\cdots ka a,2a,3a⋯ka(因为 a a a 很小),所以最小值的期望是 a a a,而此时 a a a 的期望则是 p 2 k \frac p{2k} 2kp。
所以 a , 2 a % p , 3 a % p ⋯ k a % p a,2a\%p,3a\%p\cdots ka\%p a,2a%p,3a%p⋯ka%p 是 p k \frac pk kp 这个量级的。
而根据程序验证,最小值大都处于 [ p k , 2 p k ] [\frac pk,\frac {2p}k] [kp,k2p] 之间,相对来说比较符合。
那么现在尝试用
s
t
p
stp
stp 代替
b
i
b_i
bi 来压时间。就是说现在有了一个
g
g
g 和
s
t
p
stp
stp,那么把时间按模
g
g
g 分组,每组独立做。原来的时候,第
i
i
i 个元素在
t
t
t 时刻的贡献为
a
i
+
b
i
⋅
t
−
⌊
a
i
+
b
i
⋅
t
p
i
⌋
p
i
a_i+b_i \cdot t-\lfloor \frac{a_i+b_i \cdot t}{p_i} \rfloor p_i
ai+bi⋅t−⌊piai+bi⋅t⌋pi,现在相当于把
a
i
a_i
ai 换成了相应的初值,把
b
i
b_i
bi 换成了
s
t
p
stp
stp,
t
t
t 压缩成从
0
0
0 到
T
g
\frac T{g}
gT。
然后每组还是按照一开始那个暴力做法来做,即每个时刻默认加
s
t
p
stp
stp,然后用
O
(
⌊
s
t
p
⋅
T
g
p
i
⌋
)
O(\lfloor \frac{stp \cdot \frac T{g}}{p_i} \rfloor)
O(⌊pistp⋅gT⌋) 的时间来打
−
p
i
-p_i
−pi 标记预处理。
这样一来,单个
i
i
i 的打标记复杂度变成
O
(
g
⌊
s
t
p
⋅
T
g
p
i
⌋
)
=
O
(
g
⌊
p
i
n
⋅
T
g
p
i
⌋
)
=
O
(
T
n
)
O(g\lfloor \frac{stp\cdot \frac Tg}{p_i} \rfloor)=O(g\lfloor \frac{\frac{p_i}{\sqrt n}\cdot \frac T{g}}{p_i} \rfloor)=O(\frac T{\sqrt n})
O(g⌊pistp⋅gT⌋)=O(g⌊pinpi⋅gT⌋)=O(nT),因此
n
n
n 个元素打标记总复杂度为
O
(
T
n
)
O(T\sqrt n)
O(Tn)。
然后扫一遍时间算答案的这部分,
g
g
g 相同的元素可以放一起扫,只有
n
\sqrt n
n 种不同的
g
g
g,因此这部分总时间为
O
(
n
⋅
g
T
g
)
=
O
(
T
n
)
O(\sqrt n \cdot g \frac Tg)=O(T\sqrt n)
O(n⋅ggT)=O(Tn)。
因此总时间是
O
(
T
n
)
O(T \sqrt n)
O(Tn)。
UPD:然后这题要卡常。
这题主要的卡常姿势是:在上面我们是正着走的,即
s
t
p
=
(
b
i
⋅
t
)
mod
p
i
stp=(b_i \cdot t)~\text{mod}~p_i
stp=(bi⋅t) mod pi,我们再加上个倒着走,即
s
t
p
=
−
(
p
i
−
b
i
)
⋅
t
mod
p
i
stp=-(p_i-b_i) \cdot t~\text{mod}~p_i
stp=−(pi−bi)⋅t mod pi 也拿来比较一下。这样可以优化掉一半的时间。
然后注意把 long long 换成 int。
参考
Zayin 的博客:https://blog.csdn.net/Zayin___/article/details/100529778
代码
#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long LL;
const int maxn=1e5+5, maxsqrtn=500;
int n,T,a[maxn],b[maxn],p[maxn],sqrtn,stp[maxn],sig[maxn];
LL Ans[maxn];
void ReadInt(int &data)
{
data=0;
char ch=getchar();
while (ch<'0' || ch>'9') ch=getchar();
do{
data=(data<<3)+(data<<1)+ch-'0';
ch=getchar();
} while (ch>='0' && ch<='9');
}
inline void add(int &now,const int &b,const int &p)
{
now+=b;
now=(now>=p) ?now-p :now;
}
vector<int> v[maxsqrtn];
LL tag[maxn];
int main()
{
while (scanf("%d %d",&n,&T)!=EOF)
{
fo(i,1,n) ReadInt(a[i]);
fo(i,1,n) ReadInt(b[i]);
fo(i,1,n) ReadInt(p[i]);
fo(i,0,T) Ans[i]=0;
sqrtn=sqrt(n);
fo(i,1,sqrtn) v[i].clear();
fo(i,1,n)
{
stp[i]=p[i]+5;
int now=0, g;
fo(j,1,sqrtn)
{
add(now,b[i],p[i]);
if (now<stp[i]) stp[i]=now, g=j, sig[i]=1; //正着走
if (p[i]-now<stp[i]) stp[i]=p[i]-now, g=j, sig[i]=-1; //倒着走
}
v[g].push_back(i);
}
fo(g,1,sqrtn)
{
fo(i,0,T) tag[i]=0;
LL sumstp=0;
for(int i:v[g])
{
sumstp+=stp[i]*sig[i];
for(int j=0, st=a[i]; j<g; j++, add(st,b[i],p[i]))
{
tag[j]+=st;
if (stp[i]==0) continue;
for(int now=st, t=j, tmp, nextt; ; t=nextt)
{
tmp=(sig[i]==1) ?(p[i]-1-now)/stp[i]+1 :now/stp[i]+1 ;
if (t+(LL)tmp*g>T) break;
nextt=t+tmp*g;
tag[nextt]-=p[i]*sig[i];
now+=(tmp*stp[i]-p[i])*sig[i];
}
}
}
fo(i,g,T) tag[i]+=tag[i-g]+sumstp;
fo(i,0,T) Ans[i]+=tag[i];
}
int w=0;
fo(i,1,T) if (Ans[i]>Ans[w]) w=i;
printf("%lld %d\n",Ans[w],w);
}
}