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

题目大意

  有 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+bit) 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=1n(ai+bit) 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 1n,T105,  0ai,bi<pi,  5×108<pi<109
  多测, ∑ n ≤ 1 0 6 \sum n \leq 10^6 n106,80% 数据保证 n ≤ 1000 n \leq 1000 n1000
  保证 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(npibiT) 的,因为 − 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 (bit) 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%pka%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%pka%p 等价于 a , 2 a , 3 a ⋯ k a a,2a,3a\cdots ka a,2a,3aka(因为 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%pka%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+bitpiai+bitpi,现在相当于把 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(pistpgT) 的时间来打 − 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(gpistpgT)=O(gpin pigT)=O(n T),因此 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=(bit) 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=(pibi)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);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值