题解:P12568 [UOI 2023] An Array and Range Additions
思路分析
真紫吗?
我声称,一定存在一个最优解,满足所有被操作的区间存在一个顺序,使得左右端点都是严格单调递增的。
其证明是显然的:如果出现左端点或者右端点相同,那么较长的区间可以缩一下长度。如果出现包含,例如
此时再看区间的权值。我们如果让第
显然,由于所有的区间都是严格有序的,因此被某些区间同时包含的点一定是一个连续的区间。例外是没有被区间包含的部分。这一部分可能出现两段甚至多段。
对于这种包含关系区间,显然限制就是单个区间内的数字不重复。
紧接着,我们证明一个结论:没有被区间包含的部分只出现一个前缀和一个后缀仍然不劣。
证明也是简单的。对于一个非前后缀的不操作区间,我们将其前面的操作区间的右端点扩展到自己的右端点,将其后面的操作区间的左端点扩展到自己的左端点,则自己变为被操作区间,且限制变弱,不会从有解变成无解。
从而,我们将问题转化成了下面这个非常简单的题目:
选择一段区间
假设需要拆分成
假设我们枚举了
考虑把暴力分区间优化一下。显然可以使用倍增,记录
最后考虑把枚举前后缀给优化掉。注意到前缀和后缀都相当于是进行一次“不操作”,因此我们直接断环为链,枚举从某一个位置开始先进行一次不操作之后,最少需要操作多少次才能够覆盖至少
参考代码如下:
#include<bits/stdc++.h>
using namespace std;
// #define int long long
constexpr bool online=0;
int n,a[1000005],jmp[1000005][20],v[200005],vc,lap[200005],ans;
struct seg_tree{
int v[1000005<<2];
inline void ins(int cp,int cv,int l,int r,int p){
int mid=l+r>>1; v[p]=min(v[p],cv);
if(l==r) return;
if(cp<=mid) ins(cp,cv,l,mid,p<<1);
else ins(cp,cv,mid+1,r,p<<1|1);
}
inline int que(int ql,int qr,int l,int r,int p){
if(ql<=l&&qr>=r) return v[p];
int mid=l+r>>1,ret=1e9;
if(ql<=mid) ret=min(ret,que(ql,qr,l,mid,p<<1));
if(qr>mid) ret=min(ret,que(ql,qr,mid+1,r,p<<1|1));
return ret;
}
}sgt;
signed main(){
if(online)
freopen("modify.in","r",stdin),
freopen("modify.out","w",stdout);
ios::sync_with_stdio(0); cin>>n;
/*
一定存在最优操作序列满足左右端点序列均为严格递增序列,证明显然
考虑让第 i 段的数值为 2^i*inf,则任意一个划分出来的区间之间一定不重复出现数字
特别的,我可以留一段前缀或者后缀不进行任何操作
每一段内都一定不能出现相同的数字,不操作前缀和后缀按照一段进行理解
假设我们枚举有一段前后缀不进行操作,那么:
中间剩余的部分必须划分区间
需要划分的区间数如何计算:
每一个点找到其后第一次出现数字重复的位置,即为区间右端点(开)
于是对于每一个位置处理之后,使用 ST 可以快速跳转做到 O(\log)
然而枚举前后缀的时间复杂度超了
于是我们考虑“断环为链”。虽然并不是环,但是不操作前后缀被认为是等价的,所以可以认为是环
于是枚举前后缀变成了枚举后缀起点,找最大前缀起点,然后倍增操作
此时时间复杂度为 O(n\log n),此题可解。
注意一些细节,例如:如果我有 c 个区间,那么我实际上需要操作多少次:
floor(c/2)+1,其证明是相对显然的:每一个区间最多将区间数+2,第一个区间只能做到+1
wasec,怎么快速求出其后第一次出现重复的位置?
对于左端点 p,我们找到这个数值下一次出现的位置 r
那么这个位置的 ans 就是 min(r,ans[p+1,r-1])!
证明显然,略去。
*/
for(int i=0;i!=n;++i) cin>>a[i],v[i]=a[i];
for(int i=0;i!=20;++i) jmp[n<<2][i]=n<<2;
sort(v,v+n); vc=unique(v,v+n)-v; ans=n;
for(int i=0;i!=n;++i) a[i]=lower_bound(v,v+vc,a[i])-v;
for(int i=0;i!=n<<2;++i) a[i]=a[i%n];
memset(sgt.v,0x0f,sizeof sgt.v);
for(int i=0;i!=vc;++i) lap[i]=n<<2;
for(int i=n*4-1;i>=0;i--){
jmp[i][0]=min(lap[a[i]],sgt.que(i,lap[a[i]]-1,0,n<<2,1));
lap[a[i]]=i; sgt.ins(i,jmp[i][0],0,n<<2,1);
for(int j=1;j!=20;++j) jmp[i][j]=jmp[jmp[i][j-1]][j-1];
}
for(int i=0,rp,rc,j;i!=n;++i){
for(rp=jmp[i][0],rc=0,j=19;j>=0;j--)
if(jmp[rp][j]<i+n) rc|=1<<j,rp=jmp[rp][j];
if(rp!=i+n) rc++;
rc=rc/2+1; ans=min(ans,rc);
}
if(jmp[0][0]==n) ans=0;
cout<<ans<<endl;
}
//0845 passed all samples exp 100pts.