当前位置: 首页 > news >正文

洛谷 P11830 省选联考2025 幸运数字 题解

题意

小 X 有 n n n 个正整数二元组 ( a i , b i ) ( 1 ≤ i ≤ n ) (a_i, b_i) (1 \leq i \leq n) (ai,bi)(1in)。他将会维护初始为空的可重集 S S S,并对其进行 n n n 轮操作。第 i ( 1 ≤ i ≤ n ) i (1 \leq i \leq n) i(1in) 轮操作中,他会在 S S S 中加入 a i a_i ai b i b_i bi

m = ∑ i = 1 n a i m = \sum \limits_{i=1}^{n} a_i m=i=1nai,在所有操作结束后,小 X 会得到一个包含 m m m 个正整数的可重集 S S S。最后他会计算 S S S 的中位数,即 S S S 中第 ⌊ m + 1 2 ⌋ \left\lfloor \frac{m+1}{2} \right\rfloor 2m+1 小的数,作为他的幸运数字。

想知道小 X 幸运数字的小 Y 不知道这 n n n 个二元组的具体数值是多少,但她得知了每个数的范围。具体地,对于每个 1 ≤ i ≤ n 1 \leq i \leq n 1in,小 Y 知道 a i ∈ [ l i , 1 , r i , 1 ] a_i \in [l_{i,1}, r_{i,1}] ai[li,1,ri,1] b i ∈ [ l i , 2 , r i , 2 ] b_i \in [l_{i,2}, r_{i,2}] bi[li,2,ri,2]

小 Y 想知道在满足以上条件的情况下,有多少个数可能成为小 X 的幸运数字。

∑ n \sum n n 为单个测试点内所有测试数据的 n n n 的和。对于所有测试点:

  • 1 ≤ T ≤ 400 1 \leq T \leq 400 1T400
  • 1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1n2×105 1 ≤ ∑ n ≤ 6 × 1 0 5 1 \leq \sum n \leq 6 \times 10^5 1n6×105
  • ∀ 1 ≤ i ≤ n \forall 1 \leq i \leq n ∀1in 1 ≤ l i , 1 ≤ r i , 1 ≤ 1 0 9 1 \leq l_{i,1} \leq r_{i,1} \leq 10^9 1li,1ri,1109 1 ≤ l i , 2 ≤ r i , 2 ≤ 1 0 9 1 \leq l_{i,2} \leq r_{i,2} \leq 10^9 1li,2ri,2109

思路

假如有一个数列,一个值 m m m 的个数为 b b b,比它小的数的个数为 a a a,比它大的数的个数为 c c c,对于中间位置 m i d = ⌊ a + b + c + 1 2 ⌋ mid=\left \lfloor \frac{a+b+c+1}{2} \right \rfloor mid=2a+b+c+1

  • m i d ≤ a mid\le a mida,那么中间位置在小于 m m m 的数中,中位数小于 m m m
  • a < m i d ≤ a + b a<mid\le a+b a<mida+b,那么中间位置在一堆 m m m 中,中位数就是 m m m
  • m i d > a + b mid>a+b mid>a+b,那么中间位置在大于 m m m 的数中,中位数大于 m m m

那么,当我们知道 m m m 的个数、小于和大于 m m m 的数的个数,就可以知道 m m m 是否为中位数。将这样的操作集成为函数 M i d ( a , b , c ) \rm {Mid}(a,b,c) Mid(a,b,c),三种类型分别为 − 1 , 0 , 1 -1,0,1 1,0,1

但是题目中给的是个数的区间,那要怎么办呢?

考虑从题目给的“个数区间”下手,如果可以维护两对区间:小于 m m m 的数的个数区间为 [ l l e s , r l e s ] [l_{les},r_{les}] [lles,rles],大于 m m m 的数的个数区间为 [ l b i g , r b i g ] [l_{big},r_{big}] [lbig,rbig]。如果存在 Mid ( r l e s , c n t m , l b i g ) = 0 \textup{Mid}(r_{les},cnt_m,l_{big})=0 Mid(rles,cntm,lbig)=0 或者 Mid ( l l e s , c n t m , r b i g ) \textup{Mid}(l_{les},cnt_m,r_{big}) Mid(lles,cntm,rbig),显然 m m m 为中位数。

还有两种情况就是,小于 m m m 的数的个数取得少、大于 m m m 的数的个数取得多,或者小于 m m m 的数的个数取得多、大于 m m m 的数的个数取得少。这两种情况的可行性体现于:
Mid ( r l e s , c n t m , l b i g ) ≠ Mid ( l l e s , c n t m , r b i g ) \textup{Mid}(r_{les},cnt_m,l_{big})\neq\textup{Mid}(l_{les},cnt_m,r_{big}) Mid(rles,cntm,lbig)=Mid(lles,cntm,rbig)

(感性理解就是)因为两端个数 a ∈ [ l l e s , r l e s ] a\in [l_{les},r_{les}] a[lles,rles] c ∈ [ l b i g , r b i g ] c\in[l_{big},r_{big}] c[lbig,rbig] a a a c c c 可以取各自区间的任意一个数;而“不等于”相当于存在 r l e s > l b i g , l l e s < r b i g r_{les}>l_{big},l_{les}<r_{big} rles>lbig,lles<rbig 或者 r l e s < l b i g , l l e s > r b i g r_{les}<l_{big},l_{les}>r_{big} rles<lbig,lles>rbig,可以构造出 m m m 是中位数的情况。

ll Mid(ll a,ll b,ll c)
{
	ll mid=(a+b+c+1)>>1;
	if(a>=mid)return -1;
	if(a+b>=mid)return 0;
	return 1;
}
bool MID(ll m)
{
	ll c1=Mid(les.r,m,big.l),c2=Mid(les.l,m,big.r);
	return m&&(!c1||!c2||c1!=c2);
}

那只要能维护出上文的两对区间就可以了。

因为只求个数,不需要知道具体哪一个,而且注意到题目给的 l i , 1 , r i , 1 , l i , 2 , r i , 2 l_{i,1},r_{i,1},l_{i,2},r_{i,2} li,1,ri,1,li,2,ri,2 较大,考虑离散化。然后套路地用两个动态数组分别季度左右端点的信息,以便进行枚举过程中的信息修改。

枚举一堆 m m m 是否为中位数时,贪心地取 m m m 的最多数量(比较明显的贪心)。

先初始化 l e s les les b i g big big 区间,然后直接在离散化的下标哪里扫过去:在 m m m m + 1 m+1 m+1 的转移中,使用先前维护的动态数组,分别加入和删除当前中位数和不符合条件的中位数的信息:从 b i g big big 区间删去当前中位数的信息,在 l e s les les 区间中加入被弹出的数的信息。

答案就是相邻区间个数和。使用离散后的数组就可以计算。

一些细节和写法见代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e6+9,inf=0x7f7f7f7f;
int id,T,n;
struct term
{
	ll l,r;
}a[N],b[N],les,big;
ll bb[N];
ll Mid(ll a,ll b,ll c)
{
	ll mid=(a+b+c+1)>>1;
	if(a>=mid)return -1;
	if(a+b>=mid)return 0;
	return 1;
}
bool MID(ll m)
{
	ll c1=Mid(les.r,m,big.l),c2=Mid(les.l,m,big.r);
	return m&&(!c1||!c2||c1!=c2);
}
vector<term>s[N],t[N];
void clean(ll nn)
{
	for(int i=1;i<=nn;i++)
	s[i].clear(),t[i].clear();
}
int main()
{
	scanf("%d%d",&id,&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%lld%lld%lld%lld",&a[i].l,&a[i].r,&b[i].l,&b[i].r);
			bb[i*2-1]=b[i].l;
			bb[i*2]=b[i].r+1;
		}
		sort(bb+1,bb+2*n+1);
		ll nn=unique(bb+1,bb+2*n+1)-bb-1;
		les.l=les.r=big.l=big.r=0;//在中位数枚举的转移中,动态维护两对区间
		//比中位数大/小的个数区间 
		//[les.l,les.r] cntmid [big.l,big.r]
		for(int i=1;i<=n;i++)
		{
			b[i].l=lower_bound(bb+1,bb+nn+1,b[i].l)-bb;//离散化
			b[i].r=lower_bound(bb+1,bb+nn+1,b[i].r+1)-bb;
		//	cout<<b[i].l<<" "<<b[i].r<<endl;
			s[b[i].l].push_back(a[i]);//套路地,只记录左右端点信息
			t[b[i].r].push_back(a[i]);
			big.l+=a[i].l,big.r+=a[i].r;//big区间初始化
		}
		ll cntmid=0,ret=0;
		for(int i=1;i<=nn;i++)
		{
			for(auto x:s[i])
			{
				cntmid+=x.r;//加入中位数,贪心的取数量更多的中位数 
				big.l-=x.l,big.r-=x.r;//从big区间删去
			}
			for(auto x:t[i])
			{
				cntmid-=x.r;//弹出不符合条件的 
				les.l+=x.l,les.r+=x.r;//弹出的加入les区间 
			}
		//	cout<<les.l<<" "<<les.r<<" "<<big.l<<" "<<big.r<<endl;
			if(MID(cntmid))ret+=bb[i+1]-bb[i];//这段区间都能是中位数,统计个数
		}
		printf("%lld\n",ret);
		clean(nn);
	}
	return 0;
}

相关文章:

  • 【编程题】7-3 树的同构
  • VS Code(Cursor)远程开发调试教程(超详细)
  • 自己的网页加一个搜索框,调用deepseek的API
  • 深入理解UDP:一种高效但不可靠的传输层协议、揭示其工作原理
  • 《2025软件测试工程师面试》接口测试篇
  • spark 常见操作命令
  • leetcode 148. 排序链表
  • 交叉编译 perl-5.40.0 perl-cross-1.5.3
  • 记浙江大华校招Java面试
  • Linux基础开发工具(vim编译器,yum与apt软件安装)
  • 计算机基础面试(操作系统)
  • Angular Superresolution with Antenna Pattern Errors论文阅读
  • 《数字图像处理》第一章绪论笔记:从“一图胜千言”到技术基石
  • C++-第十八章:线程相关内容
  • 如何用 TikTok 的创作工具提升你的视频质量?
  • Magic 1-For-1: 在一分钟内生成一分钟视频片段(基于Python实现,视频生成模型)
  • SpringBoot 多环境配置
  • leetcode141.环形链表,142环形链表ii
  • C++理解(六)
  • Unity3D 层级管理与标签管理详解
  • 网上商城网站建设体会/郑州百度网站快速优化
  • 长沙网站制作的/网站首页关键词如何优化
  • 网页设计商城网站建设/网站seo排名免费咨询
  • 龙岗网站建设公司官网/优化网站排名解析推广
  • 优秀网站大全/百度如何优化
  • 做psd模板下载网站/站长工具seo排名