题解:P17319 [ICPC 2018 Nanjing R] Tournament
lailai0916 · · 题解
题意简述
数轴上有
解题思路
先把每户村民分配给离它最近的体育场。沿数轴从左向右观察,分配给同一体育场的村民可以取成连续一段。否则,把两段交错的归属在分界处交换,不会增加任何人的路程。
所以只需把有序坐标划分为
设前缀和为
设
该动态规划有
令
接下来需要快速完成一次带权动态规划。固定左端点
若把左端点右移,中位数下标不会减小。坐标已经有序,所以这个增量不会增大。这正是
具体地,考虑两个分界点
从左向右计算
计算完
每个决策只会入队一次、出队一次。插入一个决策至多进行一次二分,所以一次检查的时间复杂度为
比较两个决策时,先比较带权代价。代价相同时选择段数更多的决策,使
二分最大的整数
一次检查为
正确性证明
先证明分段模型等价于原问题。任意两个体育场的最近点分界只有一个,距离相同的村民可以归入任意一侧。因此,每个体育场负责的村民可以取成连续区间。反过来,可在每个连续分段的中位数处建体育场。产生的代价恰为各段
再证明固定
假设加入新决策前,队列区间恰好覆盖全部尚未计算的位置,并记录每个位置的最优决策。删除队尾时,新决策已在该区间左端胜出,故它支配整个区间。删除停止后,新决策若能胜出,范围必为最后一个区间的后缀;二分得到其左端即可保持覆盖与最优性两个不变式。
初始时只有分界点
最后证明 WQS 二分恢复恰好
在相同最小值中取最大的
综上,算法输出的就是修建
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=300005;
struct seg
{
int x,l,r;
};
int n,k;
int a[N],g[N];
ll s[N],f[N];
seg q[N];
ll w(int l,int r)
{
int m=(l+r)>>1;
return (ll)a[m]*(m-l+1)-s[m]+s[l-1]+s[r]-s[m]-(ll)a[m]*(r-m);
}
bool better(int x,int y,int i,ll c)
{
ll u=f[x]+w(x+1,i)+c,v=f[y]+w(y+1,i)+c;
return u!=v?u<v:g[x]>g[y];
}
void add(int x,ll c,int &h,int &t)
{
while(h<=t&&better(x,q[t].x,q[t].l,c))t--;
if(h>t)
{
q[++t]={x,x+1,n};
return;
}
if(!better(x,q[t].x,n,c))return;
int l=q[t].l,r=n;
while(l<r)
{
int m=(l+r)>>1;
if(better(x,q[t].x,m,c))r=m;
else l=m+1;
}
q[t].r=l-1;
q[++t]={x,l,n};
}
int check(ll c)
{
f[0]=0;
g[0]=0;
int h=0,t=0;
q[0]={0,1,n};
for(int i=1;i<=n;i++)
{
while(q[h].r<i)h++;
int x=q[h].x;
f[i]=f[x]+w(x+1,i)+c;
g[i]=g[x]+1;
if(i==n)break;
q[h].l=i+1;
if(q[h].l>q[h].r)h++;
add(i,c,h,t);
}
return g[n];
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
s[i]=s[i-1]+a[i];
}
ll l=0,r=w(1,n)+1;
while(l<r)
{
ll m=(l+r+1)>>1;
if(check(m)>=k)l=m;
else r=m-1;
}
check(l);
cout<<f[n]-l*k<<'\n';
return 0;
}