【UER #7】天路

居然还有基于近似化答案的做法的题
/xia

先说一下自己会做的 \(70\) 分做法。

考虑 \(f_i\) 答案显然递增。

实际上求的即是 \(f_k = \min_L(\min_{a_L,..a_{L + k - 1}} - \max_{a_L,..a_{L + k - 1}} \ \ \ )\)

不如反过来考虑,令 \(g_k\) 为区间最大值和最小值差值为 \(k\) 的最大区间长度。

那么 \(f_i\)\(\min_{k > i}g_k\)

因为在随机数据下笛卡尔树树高 \(log\) ,那么直接对最小值建笛卡尔树,然后确定最小值,枚举最大值确定区间范围即可。

我写的是 \(O(nlog^2)\) 的,实际上预处理可以做到 \(O(nlog)\)

点击查看代码
//晦暗的宇宙,我们找不到光,看不见尽头,但我们永远都不会被黑色打倒。——Quinn葵因
#include<bits/stdc++.h>
#define ll long long
#define N 100005

int n;

int a[N];

int st[N],top;

int ls[N],rs[N];

inline void ins(int x){
	int las = 0;
	while(top && a[st[top]] > a[x])las = st[top],-- top;
	if(top)rs[st[top]] = x;if(las)ls[x] = las;
	st[++top] = x;
}

int lg[N],S[N][20];

inline int MAX(int x,int y){int len = y - x + 1;return std::max(S[x][lg[len]],S[y - (1ll << lg[len]) + 1][lg[len]]);}

int root;

int L[N],R[N];

#define mid ((l + r) >> 1)

int TS[N * 10];

inline void dfs(int x){
	L[x] = x,R[x] = x;
	if(ls[x])dfs(ls[x]),L[x] = L[ls[x]];
	if(rs[x])dfs(rs[x]),R[x] = R[rs[x]];
//	std::cout<<"DEL "<<x<<" "<<L[x]<<" "<<R[x]<<"\n";	
	for(int i = L[x];i < x;++i){
		int l = i,r = R[x],Ri = i,Li;while(l <= r){if(MAX(i,mid) == a[i])Ri = mid,l = mid + 1;else r = mid - 1;}
		if(Ri < x)continue; 
		l = L[x],r = i,Li = i;while(l <= r){if(MAX(mid,i) == a[i])Li = mid,r = mid - 1;else l = mid + 1;}
//		std::cout<<"("<<a[i]<<" "<<a[x]<<")"<<" "<<Li<<" "<<Ri<<"\n";
		int len = Ri - Li + 1;TS[a[i] - a[x]] = std::max(len,TS[a[i] - a[x]]);
 	}
 	for(int i = R[x];i > x;--i){		 	
		int l = L[x],r = i,Li = i,Ri;while(l <= r){if(MAX(mid,i) == a[i])Li = mid,r = mid - 1;else l = mid + 1;}
//		std::cout<<"RIGHT "<<i<<" "<<Li<<" "<<Ri<<"\n";			
		if(Li > x)continue; 
		l = i,r = R[x],Ri = i;while(l <= r){if(MAX(i,mid) == a[i])Ri = mid,l = mid + 1;else r = mid - 1;}
//		std::cout<<"("<<a[i]<<" "<<a[x]<<")"<<" "<<Li<<" "<<Ri<<"\n";
		int len = Ri - Li + 1;TS[a[i] - a[x]] = std::max(len,TS[a[i] - a[x]]);	 
	}
} 

int fans[N];

int main(){
//	freopen("t.in","r",stdin);
//	freopen("t.out","w",stdout);
	scanf("%d",&n);
	for(int i = 1;i <= n;++i)scanf("%d",&a[i]);
	for(int i = 1;i <= n;++i)ins(i);
	for(int i = 1;i <= n;++i)S[i][0] = a[i];
	lg[0] = -1;for(int i = 1;i <= n;++i)lg[i] = lg[i >> 1] + 1;
	for(int j = 1;j <= 20;++j)for(int i = 1;i + (1ll << j) - 1 <= n;++i)
	S[i][j] = std::max(S[i][j - 1],S[i + (1ll << (j - 1))][j - 1]);
	root = st[1];dfs(root);
	for(int i = 1;i <= n + 1;++i)fans[i] = 1e6; 
	for(int i = 1;i <= 1e6;++i)fans[TS[i]] = std::min(fans[TS[i]],i);
	for(int i = n;i >= 2;--i)fans[i] = std::min(fans[i + 1],fans[i]);
	for(int i = 2;i <= n;++i)std::cout<<fans[i]<<"\n";
}
 

接下来考虑正解。

每次找到 \(\leq las * 1.05\) 的边界点,并把区间赋值为该答案。

点击查看代码
#include<cstdio>
#include<algorithm>
#define N 100010

using namespace std;

int l1,l2,r1,r2,ans,n;
int a[N],q1[N],q2[N];

int work(int x)
{
	l1=l2=1; r1=r2=ans=0;
	for(int i=1,now=1;i<=n;i++)
	{
		while(r1>=l1&&a[q1[r1]]<a[i])r1--;
		while(r2>=l2&&a[q2[r2]]>a[i])r2--;
		q1[++r1]=i; q2[++r2]=i;
		while(a[q1[l1]]-a[q2[l2]]>x)
		{
			if(q1[l1]==now)l1++;
			if(q2[l2]==now)l2++;
			now++;
		}
		ans=max(ans,i-now+1);
	}
	return ans;
} 

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",a+i);
	for(int now=0,l=2,r;l<=n;now=max(now+1,(int)(now*1.05)))
	{
		r=work(now);
		for(;l<=r;l++)printf("%d\n",now);
	}
	return 0;
}

\(O(log1.05)\) 也是 \(log\) 阿(恼

posted @ 2022-04-22 11:37  fhq_treap  阅读(80)  评论(0)    收藏  举报