CF1712D 题解
\(\to \text{CF1712D Empty Graph}\leftarrow\)
思路:
赛时想到了一个 \(O(n\log n\log A)\) (\(A\) 表示值域)的解法。
先考虑 \(x\to y\ (x<y)\) 的最短距离 \(dis_{x,y}\) 怎么更新。
( 我们假定 \(mn_{l,r}\) 表示 \(\min\{a_l,a_{l+1}\cdots a_r\}\) )
显然 \(dis_{x,y}\) 由三部分取最小值组成:
-
\(x\) 直接到 \(y\),这部分的距离是 \(mn_{x,y}\)
-
\(x\) 先到 \(z\) 再到 \(y\),且满足 \(z<x\),这部分的距离是 \(mn_{z,x}+mn_{z,y}\)
-
\(x\) 先到 \(z\) 再到 \(y\),且满足 \(z>y\),这部分的距离是 \(mn_{x,z}+mn_{y,z}\)
显然,第二部分当 \(z=1\) 时有最小值 \(mn_{1,x}+mn_{1,y}\),第三部分当 \(z=n\) 时有最小值 \(mn_{x,n}+mn_{y,n}\)。
然后我发现答案满足单调性,于是二分答案 \(mid\),并且修改 \(a_{x\sim y}\) 中值小于 \(mid\) 的,这样对于第二部分和第三部分,由于 \(mn_{x,y}\ge mid\),实际上我们只用使 \(2\cdot mn_{1,x-1}\ge mid\) 且 \(2\cdot mn_{y+1,n}\ge mid\)。
也就是我们需要修改 \(1\le i\le x-1\) 和 \(y+1\le i\le n\) 且 \(a_i\le \lceil \frac{mid}{2} \rceil\) 中的 \(a_i\),然后判断总修改次数是否大于 \(k\) 次即可。
我们可以维护一棵主席树来计算需要修改的数,这样做的时间复杂度是 \(O(n^2\log n\log A)\) 的,显然需要一些优化。
然后我大胆猜测对于每一个数 \(y\) 能使得修改次数最小的 \(x\) 必然是 \(y-1\),赛时交了一发,然后就过了。
赛后证明一下:
如果选 \(x,y\) 的话,必然需要使得 \(a_{1\sim x-1,y+1\sim n}\ge \lceil \frac{mid}{2} \rceil\) 且 \(a_{x\sim y}\ge mid\)。
\(x=y-1\) 的话那么就可以让 \(a_{x\sim y}\ge mid\) 的 \(a_i\) 尽量少,这样限制条件就更少 (只用 \(\ge \lceil \frac{mid}{2} \rceil\)),也就使得修改的次数更少,也就说明 \(x=y-1\) 时最优,证毕。
这样的时间复杂度是 \(O(n\log n\log A)\) 的,而数据范围是 \(1\le n\le 10^5\) ,故能够通过此题。
code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int inf=1e9;
int n,k;
int a[N],rt[N];
namespace CT{
struct node{
int v,l,r;
}t[N*31];
int cnt;
inline void insert(int &p,int pre,int l,int r,int x,int k){
p=++cnt;
t[p]=t[pre];
++t[p].v;
if(l==r) return;
int mid=l+r>>1;
if(x<=mid) insert(t[p].l,t[pre].l,l,mid,x,k);
else insert(t[p].r,t[pre].r,mid+1,r,x,k);
}
inline int query(int q,int p,int l,int r,int L,int R){
if(L<=l&&r<=R) return t[p].v-t[q].v;
int mid=l+r>>1,res=0;
if(L<=mid) res+=query(t[q].l,t[p].l,l,mid,L,R);
if(mid<R) res+=query(t[q].r,t[p].r,mid+1,r,L,R);
return res;
}
}
inline bool check(int x,int i){
int now=0;
if(a[i]<x) ++now;
if(a[i-1]<x) ++now;
int num=x/2+(x%2);
if(now>k) return 0;
if(num==1) return 1;
now+=CT::query(rt[0],rt[i-2],1,inf,1,num-1);
now+=CT::query(rt[i],rt[n],1,inf,1,num-1);
//cout<<x<<" "<<i<<" "<<num<<" "<<now<<endl;
return now<=k;
}
signed main(){
int T;
cin>>T;
while(T--){
cin>>n>>k;
int ans=0;
rt[0]=0;
for(int i=1;i<=n;++i){
cin>>a[i];
CT::insert(rt[i],rt[i-1],1,inf,a[i],1);
}
for(int i=2;i<=n;++i){
int l=1,r=1e9+1;
while(l<r-1){
int mid=l+r>>1;
if(check(mid,i)) l=mid;
else r=mid;
}
ans=max(ans,l);
}
cout<<ans<<endl;
}
}
当然还有时间复杂度更加优秀的做法,具体可以去看官方题解。