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

【CF1039E】Summer Oenothera Exhibition(根号分治+LCT)

题目链接

  • 给定一个长度为 \(n\) 的序列 \(a_{1\sim n}\) 和一个常数 \(w\)
  • \(q\) 次询问,每次给定 \(k\),求至少对 \(a\) 切几刀才能使得每一段的极差不超过 \(w-k\)
  • \(1\le n,q\le10^5\)

贪心+根号分治

如果 \(k\) 确定,这就是一个经典问题:只要每次贪心,能不切就不切,一定是最优的。

然后这种问题想想都不太好优化,所以往根号方面去想。

若下一段的大小超过 \(\sqrt n\),我们可以二分段结尾暴力跳,总复杂度 \(O(q\sqrt n\log n)\)

关键在于下一段大小不超过 \(\sqrt n\) 的情况。如果按照 \(k\) 离线排序,从每个点开始的段长只会不断减小,因此我们可以维护出每个不超过 \(\sqrt n\) 的段以谁为结尾。那么就只要考虑如何把连续若干个不超过 \(\sqrt n\) 的段一次性跳掉。

LCT 维护

因为做这题之前先知道了是 LCT 题,所以很容易发现这是个 LCT 套路。

假设 \(i\) 开始一段是 \([i,p_i)\),那么选择 \(i\) 之后下一次就会选到 \(p_i\),因此从 \(i\)\(p_i\) 连边,建出一个内向森林。

每次跳到所在树的根,那么根就是接下来第一个下一段大小超过 \(\sqrt n\) 的情况,而连续的不超过 \(\sqrt n\) 的段数就是到根的点数(要除去根节点)。

虽说这一部分总复杂度也是 \(O(q\sqrt n\log n)\),但 LCT 的常数显然大得多,因此要适当调整阈值。

代码:\(O(q\sqrt n\log n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 100000
#define LN 17
#define BS 40
using namespace std;
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)
	int 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;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	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;
int n,Qt,sz,a[N+5],p[N+5],id[N+5],qk[N+5],ans[N+5];
struct OP {int x,y,k;bool operator < (Cn OP& o) Cn {return k>o.k;}}s[N*BS+5];
class LinkCutTree//LCT
{
	private:
		#define IR(x) (O[O[x].F].S[0]^x&&O[O[x].F].S[1]^x)
		#define Wh(x) (O[O[x].F].S[1]==x)
		#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
		#define PU(x) (O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz+1)
		int St[N+5];struct node {int Sz,F,S[2];}O[N+5];
		I void Ro(RI x)
		{
			RI f=O[x].F,p=O[f].F,d=Wh(x);!IR(f)&&(O[p].S[Wh(f)]=x);
			O[x].F=p,Co(O[x].S[d^1],f,d),Co(f,x,d^1),PU(f);
		}
		I void S(RI x) {RI f;W(!IR(x)) f=O[x].F,!IR(f)&&(Ro(Wh(x)^Wh(f)?x:f),0),Ro(x);PU(x);}
		I void Ac(RI x) {for(RI y=0;x;x=O[y=x].F) S(x),O[x].S[1]=y,PU(x);}
	public:
		I void Link(CI x,CI y) {Ac(y),S(y),O[y].F=x;}//连边
		I void Cut(CI x,CI y) {Ac(y),S(x),O[x].S[1]=O[y].F=0,PU(x);}//断边
		I void Jump(int& x,int& t) {Ac(x),S(x),t+=O[x].Sz-1;W(O[x].S[0]) x=O[x].S[0];S(x);}//找到所在根
}LCT;
struct RMQ//RMQ
{
	int Lg[N+5],V[LN+1][N+5];
	I void Init(RI op)//op=1记录最大值,op=-1记录最小值的相反数
	{
		RI i,j;for(Lg[0]=-1,i=1;i<=n;++i) Lg[i]=Lg[i>>1]+1,V[0][i]=a[i]*op;
		for(j=1;(1<<j)<=n;++j) for(i=1;i+(1<<j)-1<=n;++i) V[j][i]=max(V[j-1][i],V[j-1][i+(1<<j-1)]);
	}
	I int Q(CI l,CI r) {RI k=Lg[r-l+1];return max(V[k][l],V[k][r-(1<<k)+1]);}//询问区间最大值
}R1,R2;
I void J(int& x,CI k) {RI l=x,r=n,u;W(l^r) u=l+r+1>>1,R1.Q(x,u)+R2.Q(x,u)<=k?l=u:r=u-1;x=l+1;}//二分段结尾
bool cmpk(CI x,CI y) {return qk[x]<qk[y];}//将询问按k排序
int main()
{
	RI Qt,i,j,x;for(read(n,x,Qt),i=1;i<=n;++i) read(a[i]);R1.Init(1),R2.Init(-1); 
	for(i=1;i<=Qt;++i) read(qk[i]),qk[i]=x-qk[i],id[i]=i;sort(id+1,id+Qt+1,cmpk);
	RI c=0,mn,mx,lst;for(sz=sqrt(n)/8,i=1;i<=n;++i) for(mn=mx=a[i],lst=0,
		j=i+1;j<=min(n,i+sz);++j) mn=min(mn,a[j]),mx=max(mx,a[j]),mx-mn>lst&&(s[++c]=(OP){i,j,lst=mx-mn},0);//记录从每个点开始的段长变化时刻
	RI t;for(sort(s+1,s+c+1),i=Qt,j=1;i;--i)//按k从大到小枚举
	{
		W(j<=c&&s[j].k>qk[id[i]]) p[s[j].x]&&(LCT.Cut(p[s[j].x],s[j].x),0),LCT.Link(p[s[j].x]=s[j].y,s[j].x),++j;//修改段
		t=0,x=1;W(x<=n) p[x]?LCT.Jump(x,t):(++t,J(x,qk[id[i]]));ans[id[i]]=t-1;//大于sz二分暴力跳,小于sz用LCT查询
	}
	for(i=1;i<=Qt;++i) writeln(ans[i]);return clear(),0;
}
posted @ 2022-04-12 19:12  TheLostWeak  阅读(38)  评论(0编辑  收藏  举报