把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷7028】[NWRRC2017] Joker(分块+凸壳)

点此看题面

  • 一个长度为\(n\)的序列\(a\),令\(P\)为所有正数之和,\(N\)为所有负数之和,定义\(w_i=\begin{cases}\frac{a_i}P&a_i>0,\\\frac{a_i}{-N}&a_i<0\end{cases}\)
  • \(q\)次操作,每次修改某一个\(a_i\)
  • 在所有操作之前及每次操作之后,求出使\(s_i=\sum_{x=1}^iw_x\)最大的最小\(i\)
  • \(n,q\le5\times10^4\)

叉积的转化

我们先给所有\(w_i\)乘上\(P\times(-N)\)消去分母。

考虑我们记\(\vec{p_i}=(\sum_{x=1}^{i}a_x[a_x>0],\sum_{x=1}^i-a_x[a_x<0])\),并令\(\vec{S}=\vec{p_n}\),发现:

\[s_i=\sum_{x=1}^ia_x[a_x>0]\times(-N)-\sum_{x=1}^i-a_x[a_x<0]\times P=\vec{p_i}\times \vec{S} \]

也就是说,\(s_i\)被我们转化成了一个关于\(i\)的向量与一个固定向量的叉积。

由于叉积的最大值一定在凸包上,我们只要维护一个凸壳然后每次在其上二分即可。

分块维护凸壳

由于叉积是满足分配律的,我们可以用分块来维护,定义新的\(\vec{p_i}\)仅表示这个块内的前缀和。

这样一来,单点修改就只要重构一个块的凸壳就好了。

询问的时候我们枚举每个块,在这个块的凸壳上二分,给二分结果加上前面块之和与\(S\)的叉积值,然后更新答案。

复杂度为\(O(q(S+\frac nSlog S))\),取\(S=\sqrt{nlogn}\)得到最优复杂度\(O(nq\sqrt{nlogn})\)

代码:\(O(nq\sqrt{nlogn})\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define BS 888
#define BT 60
#define LL long long
using namespace std;
int n,a[N+5];struct P
{
	LL x,y;I P(Con LL& a=0,Con LL& b=0):x(a),y(b){}
	I P operator + (Con P& o) Con {return P(x+o.x,y+o.y);}
	I P operator - (Con P& o) Con {return P(x-o.x,y-o.y);}
	I LL operator ^ (Con P& o) Con {return 1LL*x*o.y-1LL*y*o.x;}
}p[N+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	#define D isdigit(oc=tc())
	int ff,OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0,ff=1;W(!D) ff=oc^'-'?1:-1;W(x=(x<<3)+(x<<1)+(oc&15),D);x*=ff;}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
struct Block
{
	int cnt,id[BS+5];P T;I void Build(CI l,CI r)//建凸壳
	{
		cnt=0,T=P();for(RI i=l;i<=r;id[++cnt]=i++)
			{p[i]=T=T+(a[i]>0?P(a[i],0):P(0,-a[i]));W(cnt>1&&((T-p[id[cnt]])^(p[id[cnt]]-p[id[cnt-1]]))>0) --cnt;}
	}
	I pair<LL,int> Q(Con P& S)//凸壳上二分
	{
		RI l=1,r=cnt,mid;W(l^r) mid=l+r-1>>1,((p[id[mid+1]]-p[id[mid]])^S)<=0?r=mid:l=mid+1;
		return make_pair(p[id[r]]^S,id[r]);
	}
}B[BT+5];
int sz,bl[N+5];P S;I void Build()//预处理
{
	RI i;for(sz=max((int)sqrt(n*log2(n)),1),i=1;i<=n;++i) bl[i]=(i-1)/sz+1;
	for(i=1;i<=bl[n];++i) B[i].Build((i-1)*sz+1,min(i*sz,n)),S=S+B[i].T;
}
I void U(CI x,CI v)//单点修改
{
	S=S-B[bl[x]].T,a[x]=v,B[bl[x]].Build((bl[x]-1)*sz+1,min(bl[x]*sz,n)),S=S+B[bl[x]].T;//重构这个块
}
I int Q()//询问
{
	if(!S.x||!S.y) return n;pair<LL,int> ans=make_pair((LL)-1e18,0),t;P T;//如果全正或全负答案必然为n
	for(RI i=1;i<=bl[n];++i) ((t=B[i].Q(S)).first+=T^S)>ans.first&&(ans=t,0),T=T+B[i].T;return ans.second;//枚举每个块,加上前面块之和与S的叉积值
}
int main()
{
	RI Qt,i;for(read(n,Qt),i=1;i<=n;++i) read(a[i]);Build(),writeln(Q());
	RI x,y;W(Qt--) read(x,y),U(x,y),writeln(Q());return clear(),0;
}
posted @ 2021-06-19 12:31  TheLostWeak  阅读(115)  评论(0编辑  收藏  举报