CF1837F Editorial for Two 2025.8.1模拟赛题解

前言

qwq感谢学长APJifengc出的模拟赛(太难了QAQ)

机房里的魔法少女说:
image

感觉T2是有一些思考难度的,况且十分容易想假(我就假了),遂写一篇题解记录一下;

题目

洛谷 CF1837F Editorial for Two

image

qwq很难懂是吧,让我们中译中一下

翻译

给定一个长度为n的序列,从中找出一个长度为k的子序列(可以不连续),然后将这个子序列划分为前后两部分,使得前半部分元素和与后半部分元素和的max最小。输出这个最小值。

en..感觉并不是很困难啊,赛时烧烤了5min就想出了一个(假)思路qwq

假做法(贪心)

假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了假完了
(起一个分割线的作用)

n个里面选k个,分成两部分求和取最大值,(毋庸置疑)做排序操作后选出前k个小的,然后做前缀和操作,遍历到前缀和>=前k个较小值总和/2,就是答案。

但是,大样例过不去!!我又烧烤了5min(走到了更深的错误里QAQ),想到k与n-k两部分中临近的元素可能权值相同位置不同,带来的贡献也不同,所以就把它们存出来,挨个进行选择。(QAQ浪费了好长时间导致完赛前也没想出正解)。

qwq完赛之后走在去食堂的路上 QAQ食堂为什么辣么远啊啊啊 ,机房大神bjt123 hack了我的思路(在此拜谢bjt大蛇/ 猫猫 %%%%%%%%%),他说:假设有5个值,选4个;
分别为1,2,10086,3,10087,你的(假)做法答案为10089,正确答案为10087

我恍然大悟!

我™想假啦!!

额,回到正题,让我们来打

正解

发现如果不进行分成两部分这个操作是一个典型的最大值最小问题,考虑二分答案,设二分的结果为是否有方案两部分和的最大值为x,且能选择的数的个数>=k

再考虑划分成两段的操作,我们可以先处理出对于每个前缀与后缀可以选择的最多的数,再枚举分界点,如果有一个分界点可以使能选的数的个数>=k,这次二分出的答案就合法,反之不合法。

那如何处理前缀与后缀呢?

考虑从小到大依次选,直到总和超过x,发现排序不太可行,那就让我们来使用大根堆吧!!!

如何使用呢?

考虑在你所选的数里加入一个y

  • 如果加入后总和<=x,不用处理;
  • 如果加入后总和>x,加入后取大根堆的堆顶元素,进行出堆操作,直到总和<=x

至此,鱼鱼终于想真了
(太蒟蒻啦QAQAQAQ)

最后,让我们来贴个

代码

#include<bits/stdc++.h>
using namespace std;
long long n,k,ans,a[300010],l,r,sum;
long long vis[300010];//记录前缀的答案 
std::priority_queue<int>q;
bool cheak(long long x)//二分出的答案 
{
	q=priority_queue<int>();//时间复杂度小优化(不使用while pop 清空,选择直接赋值 
	sum=0;//大根堆里元素的和 
	for(int i=1;i<=n;i++)//处理前缀 
	{
		sum+=a[i];
		q.push(a[i]);//入堆 
		while(sum>x)//加入a[i]后不符合答案 
		{
			sum-=q.top();//减去贡献 
			q.pop();//出堆 
		}
		vis[i]=q.size();//前缀处理到i位时,总和<x,可以选的数的个数 
	}
	sum=0;
	q=priority_queue<int>();//清空 
	for(int i=n;i>=1;i--)//处理后缀 
	{
		sum+=a[i];
		q.push(a[i]);
		while(sum>x)
		{
			sum-=q.top();
			q.pop(); 
		}
		if(vis[i-1]+q.size()>=k)//枚举分界点(qwq为什么是i-1可以自己手玩一下 
		{
			return 1;//答案合法 
		}
	}
	return 0;//不合法 
}
int main()
{
	freopen("ace.in","r",stdin);
	freopen("ace.out","w",stdout);
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
    	cin>>a[i];
    	r+=a[i];//r为二分答案的初始右端点,为所有元素的总和 
	}
	while(l<=r)//二分答案 
	{
		long long mid=(l+r)>>1;
		if(cheak(mid))//检验 
		{
			r=mid-1;//答案可能更优 
			ans=mid;//记录答案 
		}
		else
		{
			l=mid+1;//没有合法的答案,向右寻找 
		}
	}
	cout<<ans;//输出 
    return 0;
} 
//不开_______见祖宗 

\({\cal {The }}\) \({\cal {end. }}\)

posted @ 2025-08-01 22:10  BIxuan—玉寻  阅读(37)  评论(5)    收藏  举报