2019hdu多校第四场I Linear function【分块】

一个很简单的想法其实就是 ( 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+tbi)%piai+tbipiai+tbipi,然后对每个i标记一下 ⌊ a i + t ∗ b i p i ⌋ \lfloor \frac{a_i+t*b_i}{p_i}\rfloor piai+tbi改变的地方,前缀和优化一下便能得到 [ 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+Tbi次,所以这样的复杂度是 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=1npiai+Tbi)=O(i=1npiTbi)的。

上诉做法在 b i < < p i b_i << p_i bi<<pi时其实跑得是非常快的,但当 b i ≈ p i b_i \approx p_i bipi时就难以接受了。
所以我们就得想办法把 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=1Stbi%pi,则在数据随机的情况下可以证明 t b i ≈ p i S tb_i \approx \frac{p_i}{S} tbiSpi.

粗略证明(其实严格的我也不会....)
当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=1npiTtbi)=O(i=1npiTSpi)=O(i=1nST)=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=1Stbi%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=1ntbi%pi=gibi%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} tbiSpi的证明并不严格,导致最后程序的常数会非常大(最开始写的程序极限数据跑了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+tbi)%pi=(ait(pibi))%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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值