这题的链接:cf gym 101981 B
这道题使用了一个技巧–wqs二分,这是论文;
简述一下题意:
有n个人,在x轴上的
a
i
a_i
ai位置,可以在x轴上选择k个点,使得n个人的
m
i
n
{
d
i
s
(
a
i
,
x
k
)
}
min\{dis (a_i,x_k)\}
min{dis(ai,xk)}和最小。
(
n
,
k
<
=
3
e
5
)
(n,k<=3e5)
(n,k<=3e5)
暴力挺好想的:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示到
i
i
i点为止,分成
j
j
j段的最小代价;
d
p
[
i
]
[
j
]
=
m
i
n
{
d
p
[
k
]
[
j
−
1
]
+
s
u
m
(
k
+
1
,
i
)
}
dp[i][j]=min\{dp[k][j-1]+sum(k+1,i)\}
dp[i][j]=min{dp[k][j−1]+sum(k+1,i)}
其中对于选择一个点的
s
u
m
(
k
+
1
,
i
)
sum(k+1,i)
sum(k+1,i),只需要取其中的中位数就可以了;
然后对于这里,我们需要用到论文里面的一些知识。对于固定的
k
k
k,我们可以通过调整每次分段带来的代价来控制分的段数,从而使正常dp时不考虑分段的限制。这里的是通过wqs二分来确定这个分段的代价;具体代码如下
LL l=0,r=sum[n]+1,ans=0;
while(l<=r){
LL mid=(l+r)>>1;
if(check(mid)>=k) l=mid+1,ans=dp[n]-k*mid;
else r=mid-1;
}
而这里可能会出现选择 m i d mid mid会分成 k + 1 k+1 k+1段,而选择 m i d + 1 mid+1 mid+1则会出现 k − 1 k-1 k−1段;那么这种情况,答案一定会出现在 k + 1 k+1 k+1段中(这里应该是出现了分k段和分k+1段得到的答案是一致的);
这只是做出这一题的前置知识之一,要想完美解决这一题,对dp的优化也是需要考虑的;
很显然,不考虑限制的dp怎么能在较为优秀的复杂度中完成,也是一大考验;
这道题我们可以通过打表等方法发现其规律(一般都会有规律)—满足决策单调性,即对于
i
i
i点,考虑由前面两个点
x
或
y
(
x
<
y
)
x或y(x<y)
x或y(x<y)转移过来,如果
y
y
y比
x
x
x更优,那么
x
x
x在之后的更新中也不会比
y
y
y优。那么也就是每个点是最优的更新点的区间只有一段,且随着点的增大,区间不断递增,在平面上就类似一个凸包。这样就可以利用二分去寻找到这个对应的区间。复杂度为
O
(
n
l
o
g
2
n
)
O(nlog_2 n)
O(nlog2n)。
代码如下:
#include<bits/stdc++.h>
#define For(aa,bb,cc) for(int aa=(bb);aa<=(int)(cc);++aa)
using namespace std;
typedef long long LL;
const int maxn=3e5+10;
int n,k;
int a[maxn],num[maxn];
LL sum[maxn],dp[maxn];
struct intv{//p+1 is the best left-point in interval [l,r]
int l,r,p;
};
LL calc(int l,int r){
if(l>r) return 0;
return sum[r]-sum[(l+r)>>1]-sum[(l+r-1)>>1]+sum[l-1];
}
bool cmp(int x,int y,int r){
LL sx=dp[x]+calc(x+1,r),sy=dp[y]+calc(y+1,r);
return sx<=sy;
}
int find(intv x,int y){
int l=x.l,r=x.r+1;
while(l<r){
int mid=(l+r)>>1;
if(cmp(y,x.p,mid)) r=mid;
else l=mid+1;
}
return l;
}
int check(LL p){
intv q[maxn];
int l=1,r=0;
dp[0]=num[0]=0;
q[++r]=(intv){1,n,0};
For(i,1,n){
while(l<=r && q[l].r<i) ++l;
dp[i]=dp[q[l].p]+calc(q[l].p+1,i)+p;
num[i]=num[q[l].p]+1;
while(l<=r && cmp(i,q[r].p,q[r].l)) --r;
int pos=i+1;
if(l<=r) pos=find(q[r],i),q[r].r=pos-1;
q[++r]=(intv){pos,n,i};
}
return num[n];
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%d%d",&n,&k);
For(i,1,n) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
LL l=0,r=sum[n]+1,ans=0;
while(l<=r){
LL mid=(l+r)>>1;
if(check(mid)>=k) l=mid+1,ans=dp[n]-k*mid;
else r=mid-1;
}
printf("%lld\n",ans);
return 0;
}```