线段树做法,拿下你谷最劣解。
题意翻译很形象,就不说了。
思路
最大化最小值,我们很容易想到二分答案。很容易发现,答案具有单调性。
我们二分一个答案 \(x\) ,强制每次使用的区间长度都不小于 \(x\) ,然后判断可行性。
现在问题转化为怎么判断一个答案 \(x\) 是否可行。
我们发现,如果枚举左端点 \(l_i\) ,右端点的取值是在一个范围内的,即 \(r_i\in[p_i,n]\) ,然后 \(p_i\) 我们可以通过二分快速计算出来。不难发现 \(p_i\) 也是单调递增的。
现在问题转化为,可以选若干个区间 \([l,r]\) ,将区间内的所有数都减 \(1\) ,问最终是否能使所有数变为 \(0\) 。
区间减 \(1\) 比较难维护,考虑问题的等价转化,转成差分数组,我们引入 \(d\) 数组,令 \(d_i=b_i-b_{i-1}\) ,所有数为 \(0\) 等价于 \(\forall i\in[1,n],d_i=0\) ,为了程序写的方便,我们引入点 \(x_{n+1}=inf\) ,并且规定 \(d_{n+1}\) 取任意数均可。
然后问题又转化成,可以在 \([1,l_i]\) 中选一个 \(d_x\) 将其减一,在 \([p_i+1,n]\) 中选一个 \(d_x\) 使其加一。
我们从 \(1\) 到 \(n\) 遍历 ,如果遇见某一位是负数,那么就是非法的,所以有一个比较明显的贪心策略:
- 从 \(1\) 到 \(n\) 遍历 \(d_i\) ,如果 \(d_i<0\) 返回情况非法,否则,贪心的选择 \(j\in[p+1,n]\) 内当前的数和最左边的负数 \(d_j\),使其变为正数,并在 \(d_i\ge 0\) 的条件下。
贪心正确性显然,交换证明即可,可自己尝试。
最后,使用线段树动态维护最左边的负数即可。当然,使用 multiset
也可,或者一个队列维护,但是不太想写了。
使用线段树和二分,所以时间复杂度 \(O(n\log n\log w)\),强势拿下最劣解。
code
#include<bits/stdc++.h>
#define mp make_pair
#define N 200005
#define ll long long
#define Pr pair<ll,int>
#define fi first
#define se second
using namespace std;
const ll inf=1e10;
int n;
ll a[N],b[N],t[N];
ll d[N];
struct segtree{
Pr tr[N*4];
void Pushup(int x)
{
if(tr[x*2].fi>=0) tr[x]=tr[x*2+1];
else tr[x]=tr[x*2];
}
void modify(int l,int r,int P,int x,ll v)
{
if(l==r)
{
tr[x]=mp(v,l);
return;
}
int mid=(l+r)/2;
if(P<=mid) modify(l,mid,P,x*2,v);
else modify(mid+1,r,P,x*2+1,v);
Pushup(x);
}
Pr query(int l,int r,int L,int R,int x)
{
if(l>R||r<L) return mp(inf,-1);
if(l>=L&&r<=R) return tr[x];
int mid=(l+r)/2;
Pr lv=query(l,mid,L,R,x*2),rv=query(mid+1,r,L,R,x*2+1);
if(lv.fi>=0) return rv;
else return lv;
}
}tr;
bool check(ll x)
{
memcpy(b,t,sizeof t);
for(int i=1;i<=n;i++) tr.modify(1,n,i,1,b[i]);
for(int i=1;i<=n;i++)
{
if(b[i]<0) return 0;
int p=upper_bound(a,a+1+n,a[i-1]+1+x)-a;// p->n
while(b[i])
{
Pr t=tr.query(1,n,p,n,1);
ll mv=t.first;
int mp=t.second;
if(mv>=0) break;
if(mv+b[i]>0) b[i]+=mv,b[mp]=0,tr.modify(1,n,mp,1,0),tr.modify(1,n,i,1,b[i]);
else
{
b[mp]+=b[i];
b[i]=0;
tr.modify(1,n,mp,1,b[mp]);
tr.modify(1,n,i,1,0);
break;
}
}
}
return 1;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=n;i++) scanf("%lld",&t[i]);
for(int i=n;i>=1;i--) t[i]-=t[i-1];
a[0]=-inf,a[n+1]=inf;
ll l=1,r=1e9+5;
while(l<=r)
{
ll mid=(l+r)/2;
if(check(mid)) l=mid+1;
else r=mid-1;
}
if(l>1e9) printf("-1");
else printf("%lld",l-1);
return 0;
}
标签:return,int,ll,tr,小计,做题,mp,arc166D,define
From: https://www.cnblogs.com/g1ove/p/18147282