【2019 Multi-University 4 I】Linear Functions 题解

题目大意

  有 nn 个元素,第 ii 个元素在初始 00 时刻时值为 aia_i,此后每个时刻增加 bib_i 并模 pip_i,即在 tt 时刻时值为 (ai+bit)modpi(a_i+b_i\cdot t) \bmod p_i,其中 tt 为整数。
  求

maxt=0T{i=1n(ai+bit)modpi}\max_{t=0}^T \{\sum_{i=1}^n (a_i+b_i\cdot t) \bmod p_i \}

  输出这个最大值,及其对应的最早的时刻。

  1n,T105,  0ai,bi<pi,  5×108<pi<1091 \leq n,T \leq 10^5,\ \ 0 \leq a_i,b_i < p_i,\ \ 5\times10^8 < p_i < 10^9
  多测,n106\sum n \leq 10^6,80% 数据保证 n1000n \leq 1000
  保证 pip_i 为质数;
  ai,bia_i,b_i[0,pi)[0,p_i) 范围内随机生成;
  5s

题解

  鉴于目前这题只有两篇不讲人话的题解(官方题解和 Zayin 的博客),我决定补掉这题并写一篇世人能看得懂的题解

  首先思考一些比较暴力的做法,一开始令 ans=i=1naians=\sum_{i=1}^n a_i,然后每往后走一个时刻默认给 ansans 加上 i=1nbi\sum_{i=1}^n b_i,然后我们对于每个 ii 事先标记出哪些时刻要让 ans=pians-=p_i,这样就可以算出答案了。
  这样乍一看是 O(nT)O(nT) 的,但写准确一点应该是 O(nbiTpi)O(n\lfloor\frac{b_iT}{p_i}\rfloor) 的,因为 pi-p_i 的标记只有 ai+biTpi\lfloor\frac{a_i+b_iT}{p_i}\rfloor 个,因此单个 ii 的预处理是 O(biTpi)O(\lfloor\frac{b_iT}{p_i}\rfloor) 的。
  这在 bib_i 小的时候很好,在 bib_i 大的时候(接近 pip_i 的时候)就变成 O(nT)O(nT) 了。

  所以接下来要用一些姿势,使得 bib_i 能够变小。

  举个例子观察一下,比如 bi=4, pi=11b_i=4,~p_i=11,那么 (bit) mod pi(b_i\cdot t)~\text{mod}~p_i 会长这个样子:

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

  记 G=nG=\lfloor \sqrt n \rfloor,比如我们假设 G=4G=4,那么观察前 44 个数,记 stpstp 表示这前 GG 个数的最小值,gg 表示最小值在第几个取到。(这个例子中 stp=1, g=3stp=1,~g=3
  把时间 tt 按模 gg 分组,则可以发现,这个数列分成了 gg 组,每组起始值不同,间隔都是 stpstp并且 stpstp 比较小

  stpstp 有多小呢?在 bib_i 随机的情况下是 O(piG)O(\frac{p_i}{G}) 的。这里引用一下 Zayin 的粗略证明:

  粗略证明(其实严格的我也不会....)
  当 aa 很大(比如 a>pka>\frac pk)时,a,2a%p,3a%pka%pa,2a\%p,3a\%p\cdots ka\%p 这个数列几乎可以认为是随机的,而在 [0,p)[0,p) 中随机选 kk 个数的最小值的期望是 Pk+1\frac P{k+1}
  当 aa 很小(比如 a<pka<\frac pk)时,a,2a%p,3a%pka%pa,2a\%p,3a\%p\cdots ka\%p 等价于 a,2a,3akaa,2a,3a\cdots ka(因为 aa 很小),所以最小值的期望是 aa,而此时 aa 的期望则是 p2k\frac p{2k}
  所以 a,2a%p,3a%pka%pa,2a\%p,3a\%p\cdots ka\%ppk\frac pk 这个量级的。
  而根据程序验证,最小值大都处于 [pk,2pk][\frac pk,\frac {2p}k] 之间,相对来说比较符合。

  那么现在尝试用 stpstp 代替 bib_i 来压时间。就是说现在有了一个 ggstpstp,那么把时间按模 gg 分组,每组独立做。原来的时候,第 ii 个元素在 tt 时刻的贡献为 ai+bitai+bitpipia_i+b_i \cdot t-\lfloor \frac{a_i+b_i \cdot t}{p_i} \rfloor p_i,现在相当于把 aia_i 换成了相应的初值,把 bib_i 换成了 stpstptt 压缩成从 00Tg\frac T{g}
  然后每组还是按照一开始那个暴力做法来做,即每个时刻默认加 stpstp,然后用 O(stpTgpi)O(\lfloor \frac{stp \cdot \frac T{g}}{p_i} \rfloor) 的时间来打 pi-p_i 标记预处理。

  这样一来,单个 ii 的打标记复杂度变成 O(gstpTgpi)=O(gpinTgpi)=O(Tn)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}),因此 nn 个元素打标记总复杂度为 O(Tn)O(T\sqrt n)
  然后扫一遍时间算答案的这部分,gg 相同的元素可以放一起扫,只有 n\sqrt n 种不同的 gg,因此这部分总时间为 O(ngTg)=O(Tn)O(\sqrt n \cdot g \frac Tg)=O(T\sqrt n)
  因此总时间是 O(Tn)O(T \sqrt n)

  UPD:然后这题要卡常。
  这题主要的卡常姿势是:在上面我们是正着走的,即 stp=(bit) mod pistp=(b_i \cdot t)~\text{mod}~p_i,我们再加上个倒着走,即 stp=(pibi)t mod pistp=-(p_i-b_i) \cdot t~\text{mod}~p_i 也拿来比较一下。这样可以优化掉一半的时间。
  然后注意把 long long 换成 int。

参考

  Zayin 的博客:https://blog.csdn.net/Zayin___/article/details/100529778

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#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);
}
}