1. 程式人生 > 其它 >【洛谷7028】[NWRRC2017] Joker(分塊+凸殼)

【洛谷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;
}