POI 2012 STU-Well
POI 2012 STU-Well
题意翻译
给定一个非负整数序列A,每次操作可以选择一个数然后减掉1,要求进行不超过m次操作使得存在一个A_k=0A**k=0且\max(|x_i-x_{i-1}|)max(∣x**i−x**i−1∣)最小,输出这个最小值以及此时最小的k
题解:
首先看到最大值最小就考虑二分答案。
直接二分的就是最大斜率最小值。我们进行扫描,如果碰到比当前二分到的答案大的就把它变小,然后递减操作次数。因为是绝对值,所以要正反扫两遍。
之后我们考虑如何挑一个k使之得0。
我们发现,对于我们目前二分到的答案,如果有一个位置\((k,0)\),那么其可行区间一定是过这个点,斜率为二分到的答案的下面的部分。如果有点在上面,肯定是不符合题意的,要减下来。我们又发现,这样的点一定是连续的分布在当前枚举到的K的两侧。所以可以用一个类似于滑动窗口的东西来维护这里。碰到第一个符合题意的就跳出。
代码:
#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=1e6+6;
int n,m,ans;
int a[maxn],b[maxn],sum[maxn];
int check(int x)
{
int tot=0;
for(int i=1;i<=n;i++)
b[i]=a[i];
b[n+1]=-1;
for(int i=2;i<=n;i++)
if(b[i]-b[i-1]>x)
{
tot+=(b[i]-b[i-1]-x);
b[i]=b[i-1]+x;
}
if(tot>m)
return 0;
for(int i=n-1;i>=1;i--)
if(b[i]-b[i+1]>x)
{
tot+=(b[i]-b[i+1]-x);
b[i]=b[i+1]+x;
}
if(tot>m)
return 0;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+b[i];
int l=1,r=1;
for(int i=1;i<=n;i++)
{
int tmp=b[i];
b[i]=0;
while(b[l]<x*(i-l))
l++;
while(b[r+1]>=x*(r-i+1))
r++;
b[i]=tmp;
int all=(1ll*x*((1ll*(i-l)*(i-l+1)+1ll*(r-i)*(r-i+1))/2));
if(m-tot>=(sum[r]-sum[l-1])-all)
return i;
}
return 0;
}
signed main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
if(check(0))
{
printf("%lld %lld\n",check(0),0);
return 0;
}
int l=0,r=1e9;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid))
r=mid;
else
l=mid+1;
}
printf("%lld %lld\n",check(r),r);
return 0;
}