P6246 [IOI 2000] 邮局 加强版 加强版

简要题意

看题面,还是比较好理解的。

前置知识

wqs二分,决策单调性

思路

  1. 首先我们回忆一下非加强版的做法。设 \(f_{i,j}\) 表示在前 \(i\) 个村建 \(j\) 个邮局的最小距离总和。转移非常的简单枚举上一段的结尾即可。一段区间建一个邮局显然建在中点最优,带来的贡献可以用前缀和 \(O(1)\) 计算。(就是邮局左边右边分开讨论)

\[f_{i,j}=\min\{f_{k,j-1}+w(k+1,i)\} \]

  1. 这样的时间复杂度显然不优,我们可以关注代价函数,猜测其有四边形不等式。(严谨证明)然后用决策单调性的性质就可做到 \(O(nk\log n)\) 可以通过强化版,但不能通过这道题。
  2. 观察数据范围,我们发现主要的变化就是 \(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;
 } 
posted @ 2025-05-06 16:30  exCat  阅读(29)  评论(0)    收藏  举报