P4767 [IOI2000]邮局
wqs 二分+四边形不等式优化 dp
最朴素的dp是 (f_{i,j}) 表示前 (i) 个村庄设立了 (j) 个邮局的最有答案,但这样每个状态转移是 (O(V)),总复杂度 (O(V^2P))
于是可以用 wqs 二分,每次增加一个邮局就多付出一个权值,二分这个权值即可,至于正确性大概就是一段区间内邮局放的越多,距离和减小的越少
然后此时就 (O(Vlog w)) 了(其中 (w) 指距离),但仍可以继续优化。记录 (sum_i) 为前缀和,那么 ([l,r]) 中建立一个邮局所产生的最小距离和就是 (w(l,r)=sum_r+sum_{l-1}-sum_{mid}-sum_{mid-2}),其中 (mid=frac{l+r}{2})
然后按四边形不等式的定义减一减就可以知道它是满足四边形不等式的,于是 wqs 二分的 check 可以用决策单调性优化,那么就做到了 (O(Vlog Vlog w))
忘了怎么写决策队列了,一直写错,调吐了
#include<cstdio>
#include<cmath>
#include<algorithm>
#define reg register
inline int read(){
register int x=0;register int y=1;
register char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=getchar();}
return y?x:-x;
}
#define N 100006
int n,k;
long long a[N],sum[N];
long long sumD,C;
struct DP{
long long val;int num;
}f[N];
struct Q{
int l,r,id;
}que[N];
int left,right;
inline int operator < (const DP &a,const DP &b){return a.val==b.val?a.num>b.num:a.val<b.val;}
inline int operator <= (const DP &a,const DP &b){return a.val==b.val?a.num>=b.num:a.val<=b.val;}
inline DP operator + (const DP &a,const DP &b){return (DP){a.val+b.val,a.num+b.num};}
inline DP calc(reg int j,reg int i){//[j,i]
// if(j==i) return 0;
// if(j+1==i) return a[i]-a[j];
j++;
int mid=(i+j)>>1;
return (DP){sum[i]+sum[j-1]-sum[mid]-sum[mid-1]+(2*mid-i-j)*a[mid]+C,1}+f[j-1];
}
inline int find(reg int l,reg int r,int id,int cmp_id){
reg int mid,ans;
while(l<=r){
mid=(l+r)>>1;
if(calc(id,mid)<=calc(cmp_id,mid)) ans=mid,r=mid-1;
else l=mid+1;
}
return ans;
}
inline int check(){
f[0]=(DP){0,0};
left=right=0;que[0]=(Q){1,n,0};
for(reg int i=1;i<=n;i++){
while(que[left].r<i&&left<=right) left++;
f[i]=calc(que[left].id,i);
if(i==n||calc(que[right].id,n)<calc(i,n)) continue;
while(left<=right&&calc(i,que[right].l)<=calc(que[right].id,que[right].l)) right--;
if(left>right) que[++right]=(Q){i+1,n,i};
else{
int pos=find(i+1,n,i,que[right].id);
que[right].r=pos-1;que[++right]=(Q){pos,n,i};
}
}
sumD=f[n].val;
return f[n].num;
}
int main(){
n=read();k=read();
for(reg int i=1;i<=n;i++) a[i]=read(),sum[i]=sum[i-1]+a[i];
reg long long l=0,r=1e14,ans;
while(l<=r){
C=(l+r)>>1;
if(check()>=k) l=C+1,ans=sumD-k*C;
else r=C-1;
}
// int kk=check(ans);
// printf("%lld in %lld,kk=%d
",sumD-k*ans,ans,kk);
printf("%lld
",ans);
// printf("debug : %lld
",calc(50,1));
return 0;
}