P6246 [IOI 2000] 邮局 加强版 加强版
简要题意
看题面,还是比较好理解的。
前置知识
wqs二分,决策单调性
思路
- 首先我们回忆一下非加强版的做法。设 \(f_{i,j}\) 表示在前 \(i\) 个村建 \(j\) 个邮局的最小距离总和。转移非常的简单枚举上一段的结尾即可。一段区间建一个邮局显然建在中点最优,带来的贡献可以用前缀和 \(O(1)\) 计算。(就是邮局左边右边分开讨论)
\[f_{i,j}=\min\{f_{k,j-1}+w(k+1,i)\}
\]
- 这样的时间复杂度显然不优,我们可以关注代价函数,猜测其有四边形不等式。(严谨证明)然后用决策单调性的性质就可做到 \(O(nk\log n)\) 可以通过强化版,但不能通过这道题。
- 观察数据范围,我们发现主要的变化就是 \(k\) 的范围。这还有一个 \(trick\) , 对于这种最后恰好分成 \(j\) 组的 dp ,在 \(j\) 比较大的时候都可以考虑 \(wqs\) 二分。只需要证明 \(f_{i,j}+j\times t\) 也具有凸性。(其实不需要,我们有四边形不等式就可以[证明 \(f_{i,j}\) 的凸性](四边形不等式优化 - OI Wiki),然后我们知道凸函数加一次函数还是凸的,所以可以直接做)。所以就做完了。
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e5+10;
int n,m;ll a[N],sum[N];
struct stu
{
int k,l,r;
};
deque<stu> q;
struct node
{
ll fi;int se;
}dp[N];
ll w(int l,int r)//o(1)计算贡献
{
ll mid=(l+r)>>1,ans=0;
ans=1ll*a[mid]*(mid-l)-(sum[mid-1]-sum[l-1]);//左边的贡献
ans+=(sum[r]-sum[mid])-1ll*a[mid]*(r-mid);
return ans;
}
bool check1(int i,int j,int k)
{
return dp[i].fi+w(i+1,k)<=dp[j].fi+w(j+1,k);//注意等于
}
node check(ll x)
{
while(!q.empty())q.pop_back();
q.push_back({0,1,n});dp[0]={0,0};
for(int i=1;i<=n;i++)
{
while(!q.empty()&&q.front().r<i)q.pop_front();
q.front().l=i;
int j=q.front().k;
dp[i]={dp[j].fi+w(j+1,i)+x,dp[j].se+1};
while(!q.empty()&&check1(i,q.back().k,q.back().l))q.pop_back();//i和q.back().k 都可以转时,用i转段数会更多,所以主函数二分要取右边
if(!q.empty())
{
int l=q.back().l,r=n,ans=-1,k=q.back().k;
while(l<=r)
{
int mid=(l+r)>>1;
if(check1(i,k,mid))ans=mid,r=mid-1;//一样相同时要i
else l=mid+1;
}
if(ans!=-1)q.back().r=ans-1,q.push_back({i,ans,n});
}
else q.push_back({i,1,n});
}
return dp[n];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];
ll l=0,r=1e12,ans=0;
while(l<=r)
{
ll mid=(l+r)>>1;
// cout<<l<<" "<<r<<" "<<mid<<'\n';
if(check(mid).se>=m)ans=mid,l=mid+1;//三点共线取右边的(分的段多的情况)
else r=mid-1;
}
//cout<<ans<<'\n';
cout<<check(ans).fi-m*ans<<'\n';
return 0;
}

浙公网安备 33010602011771号