icpc2018南京站B题 tournament

1 篇文章 0 订阅

这题的链接: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][j1]+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 k1段;那么这种情况,答案一定会出现在 k + 1 k+1 k+1段中(这里应该是出现了分k段和分k+1段得到的答案是一致的);

这只是做出这一题的前置知识之一,要想完美解决这一题,对dp的优化也是需要考虑的;
很显然,不考虑限制的dp怎么能在较为优秀的复杂度中完成,也是一大考验;
这道题我们可以通过打表等方法发现其规律(一般都会有规律)—满足决策单调性,即对于 i i i点,考虑由前面两个点 x 或 y ( x < y ) x或y(x<y) xy(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;
}```

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值