一个很简单的想法其实就是 ( a i + t ∗ b i ) % p i ⇒ a i + t ∗ b i − ⌊ a i + t ∗ b i p i ⌋ ∗ p i (a_i+t*b_i) \% p_i \Rightarrow a_i+t*b_i - \lfloor \frac{a_i+t*b_i}{p_i}\rfloor*p_i (ai+t∗bi)%pi⇒ai+t∗bi−⌊piai+t∗bi⌋∗pi,然后对每个i标记一下 ⌊ a i + t ∗ b i p i ⌋ \lfloor \frac{a_i+t*b_i}{p_i}\rfloor ⌊piai+t∗bi⌋改变的地方,前缀和优化一下便能得到 [ 0 , T ] [0,T] [0,T]的答案。
因为每个i恰好标记 ⌊ a i + T ∗ b i p i ⌋ \lfloor \frac{a_i+T*b_i}{p_i}\rfloor ⌊piai+T∗bi⌋次,所以这样的复杂度是 O ( ∑ i = 1 n ⌊ a i + T ∗ b i p i ⌋ ) = O ( ∑ i = 1 n ⌊ T ∗ b i p i ⌋ ) O(\sum_{i=1}^n\lfloor \frac{a_i+T*b_i}{p_i}\rfloor)=O(\sum_{i=1}^n\lfloor \frac{T*b_i}{p_i}\rfloor) O(∑i=1n⌊piai+T∗bi⌋)=O(∑i=1n⌊piT∗bi⌋)的。
上诉做法在
b
i
<
<
p
i
b_i << p_i
bi<<pi时其实跑得是非常快的,但当
b
i
≈
p
i
b_i \approx p_i
bi≈pi时就难以接受了。
所以我们就得想办法把
b
i
b_i
bi给降下来。
若取 S = O ( s q r t ( n ) ) S=O(sqrt(n)) S=O(sqrt(n)),并令 t b i = min t = 1 S t ∗ b i % p i tb_i=\min_{t=1}^S t*b_i\%p_i tbi=mint=1St∗bi%pi,则在数据随机的情况下可以证明 t b i ≈ p i S tb_i \approx \frac{p_i}{S} tbi≈Spi.
粗略证明(其实严格的我也不会....)
当a很大(比如a>p/k)时,a,2a%p,3a%p...ka%p这个数列几乎可以认为是随机的,而在[0,p)中随机选k个数的最小值的期望是P/(k+1).
当a很小(比如a<p/k)时,a,2a%p,3a%p...ka%p等价于a,2a,3a...ka(因为a很小),所以最小值的期望是a,而此时a的期望则是p/2k。
所以a,2a%p,3a%p...ka%p是p/k这个量级的。
而根据程序验证,最小值大都处于[p/k,2p/k]之间,相对来说比较符合。
此时如果把
t
b
i
tb_i
tbi带入上述暴力的复杂度,则:
O
(
∑
i
=
1
n
⌊
T
∗
t
b
i
p
i
⌋
)
=
O
(
∑
i
=
1
n
⌊
T
∗
p
i
S
p
i
⌋
)
=
O
(
∑
i
=
1
n
⌊
T
S
⌋
)
=
O
(
n
T
S
)
=
O
(
T
n
)
O(\sum_{i=1}^n\lfloor \frac{T*tb_i}{p_i}\rfloor)=O(\sum_{i=1}^n\lfloor \frac{T*\frac{p_i}{S}}{p_i}\rfloor)=O(\sum_{i=1}^n\lfloor \frac{T}{S}\rfloor)=O(\frac{nT}{S})=O(T\sqrt n)
O(∑i=1n⌊piT∗tbi⌋)=O(∑i=1n⌊piT∗Spi⌋)=O(∑i=1n⌊ST⌋)=O(SnT)=O(Tn)
但注意到此时的
t
b
i
tb_i
tbi不能根据之前那样直接标记了。
若令
g
i
g_i
gi在
M
i
n
t
=
1
S
t
∗
b
i
%
p
i
Min_{t=1}^S t*b_i\%p_i
Mint=1St∗bi%pi取得最小值,即
M
i
n
t
=
1
n
t
∗
b
i
%
p
i
=
g
i
∗
b
i
%
p
i
Min_{t=1}^n t*b_i\%p_i=g_i*b_i\%p_i
Mint=1nt∗bi%pi=gi∗bi%pi。
则
t
b
i
tb_i
tbi相当于是一次加了
g
i
g_i
gi个
b
i
b_i
bi,所以时间
t
t
t要按照
g
i
g_i
gi分块来分段做。
而且此时前缀和也不能只做一次了,因为每个i进行分块的间隔都不相同。
但也不能每个i单独做前缀和,不然复杂度就退化成
O
(
n
T
)
O(nT)
O(nT)了。
所以最后要把
g
i
g_i
gi相同的一次做完,再做一边前缀和,这部分的复杂度也是
O
(
T
n
)
O(T\sqrt n)
O(Tn)。
你以为这就完了?安心去卡常吧
但由于上面关于
t
b
i
≈
p
i
S
tb_i \approx \frac{p_i}{S}
tbi≈Spi的证明并不严格,导致最后程序的常数会非常大(最开始写的程序极限数据跑了23s…)。
所以可以同时利用
(
a
i
+
t
∗
b
i
)
%
p
i
=
(
a
i
−
t
∗
(
p
i
−
b
i
)
)
%
p
i
(a_i+t*b_i) \% p_i=(a_i-t*(p_i-b_i)) \% p_i
(ai+t∗bi)%pi=(ai−t∗(pi−bi))%pi优化一半的常数。
还要尽量不做除法不取模,把常数优化到最小才能通过这道题(随便吐槽一句,hdu上快读竟然不起作用…)
(可能是因为我比较菜,所以写出来的常数比较大才要卡常?)
所以hdu上那两位跑进了3s的仁兄到底是怎么写的
#include<bits/stdc++.h>
#define maxn 100050
using namespace std;
typedef long long LL;
int S;
int n,T;
int a[maxn],b[maxn],p[maxn];
vector<int> G[maxn];
int tb[maxn],g[maxn],sgn[maxn];
LL tag[maxn],sum[maxn];
inline void add(int &a,const int &b,const int& p) {
a+=b;
if (a>=p) a-=p;
}
int main() {
while (~scanf("%d%d",&n,&T)) {
for (int i=1;i<=n;++i) scanf("%d",a+i);
for (int i=1;i<=n;++i) scanf("%d",b+i);
for (int i=1;i<=n;++i) scanf("%d",p+i);
S=0.7*sqrt(n)+5;
for (int i=0;i<=S;++i) G[i].clear();
for (int i=0;i<=T;++i) sum[i]=0;
for (int i=1;i<=n;++i) {
int t=0;
tb[i]=p[i];
for (int j=1;j<=S;++j) {
add(t,b[i],p[i]);
if (tb[i]>t)
tb[i]=t,sgn[i]=1,g[i]=j;
if (tb[i]>p[i]-t)
tb[i]=p[i]-t,sgn[i]=-1,g[i]=j;
}
G[g[i]].push_back(i);
}
for (int s=1;s<=S;++s) {
if (!G[s].size()) continue;
for (int t=0;t<=T;++t) tag[t]=0;
LL sumb=0;
for (int i:G[s]) {
if (sgn[i]==1) {
sumb+=tb[i];
for (int st=0,ta=a[i];st<s&&st<=T;++st,add(ta,b[i],p[i])) {
int Lim=(T-st)/s;
tag[st]+=ta;
if (!tb[i]) continue;
for (LL kp=p[i]-ta-1;;kp+=p[i]) {
LL l=kp/tb[i]+1;
if (l>Lim) break;
tag[st+l*s]-=p[i];
}
}
} else {
sumb-=tb[i];
for (int st=0,ta=a[i];st<s&&st<=T;++st,add(ta,b[i],p[i])) {
int Lim=(T-st)/s;
tag[st]+=ta;
if (!tb[i]) continue;
for (LL kp=ta;;kp+=p[i]) {
LL l=kp/tb[i]+1;
if (l>Lim) break;
tag[st+l*s]+=p[i];
}
}
}
}
for (int st=0;st<s;++st)
for (int j=st+s;j<=T;j+=s)
tag[j]+=tag[j-s]+sumb;
for (int t=0;t<=T;++t)
sum[t]+=tag[t];
}
int t=0;
for (int i=0;i<=T;++i)
if (sum[i]>sum[t])
t=i;
printf("%lld %d\n",sum[t],t);
}
return 0;
}