CF2183H - Minimise Cost

题目链接

先把 \(a _ i\) 从小到大排序,一定会取连续的 \(k\) 段,且长度单调不增。忽略第二个条件,这样可以设计出一个 \(\text O (n ^ 2 k)\) 的 dp。

若所有 \(a _ i \ge 0\),则 dp 有决策单调性;考虑原题,假设把 \(0\) 归到负数,若 $k > $ 正数个数,则一定是把前面 \(n - k + 1\) 个负数分一组,后面每个数自成一组,这可以 \(\text O (n)\) 解决;否则,负数一定都在一组。

猜一个结论:若把禁用掉负数段中间的决策点,即强制负数不能断开,dp 仍满足决策单调性。于是可以优化到 \(\text O (nk \log n)\)

继续观察到答案关于 \(k\) 是凸的,证明 OI Wiki 上有,于是在外层套上 wqs 二分,时间优化到 \(\text O (n \log n \log V)\)

#include<cstdio>
#include<algorithm>
#define N 200005
using namespace std;

typedef long long ll;
typedef __int128 lll;
int T,n,k,p,a[N],g[N];
int lt,rt,q[N],t[N];
ll s[N];
lll f[N];
void write(lll x) {
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar('0'+x%10);
}
int search(int x,int y) {
	int l=x,r=n+1;
	while(l<r) {
		int mid=l+r>>1;
		if(f[x]+(lll)(s[mid]-s[x])*(mid-x)<=f[y]+(lll)(s[mid]-s[y])*(mid-y)) r=mid;
		else l=mid+1;
	}
	return l;
}
void wqs(lll cost) {
	f[0]=g[0]=0;
	lt=1,rt=0,q[++rt]=0;
	for(int i=max(p,1),j;i<=n;i++) {
		while(lt<rt&&t[lt+1]<=i) lt++;
		j=q[lt],f[i]=f[j]+(lll)(s[i]-s[j])*(i-j)-cost,g[i]=g[j]+1;
		while(lt<rt&&search(i,q[rt])<t[rt]) rt--;
		q[++rt]=i,t[rt]=search(i,q[rt-1]);
	}
}
int main() {
	scanf("%d",&T);
	XJX:while(T--) {
		scanf("%d%d",&n,&k);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		sort(a+1,a+n+1),p=0;
		for(int i=1;i<=n;i++) {
			s[i]=s[i-1]+a[i];
			if(a[i]<=0) p=i;
		}
		if(k>n-p) {
			int l=n-k+1; lll ans=0;
			for(int i=1;i<=l;i++) ans+=1ll*a[i]*l;
			for(int i=l+1;i<=n;i++) ans+=a[i];
			write(ans),putchar('\n'); goto XJX;
		}
		lll l=-2e19,r=2e19;
		while(l<r) {
			lll mid=l+r>>1; wqs(mid);
			if(g[n]>=k) r=mid; else l=mid+1;
		}
		wqs(l),write(f[n]+(lll)l*k),putchar('\n');
	}
	return 0;
}

/*
1
5 4
0 2 0 -3 0 

记得把 0 归到负数!
*/
posted @ 2026-01-14 16:44  yemuzhe  阅读(3)  评论(0)    收藏  举报