居然还有基于近似化答案的做法的题
/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\) 阿(恼