Codeforces 612 D. The Union of k-Segments (非递归线段树+离散化)

题意:给定一堆线段,求最后重叠了k次或以上的线段和点。

先操作,最后一次下推标记,所以尽管是区间修改,非递归写起来还是很简单。

维护两个线段树,一个维护线段的覆盖,一个维护点的覆盖。

对于线段[L,R],点修改的区间是[L,R],

区间修改中,用线段的左端点代表这条线段,所以区间修改的区间是[L,R-1]


在所有操作都结束之后下推标记,然后从左到右扫描线段输出答案即可。

输出答案的时候,只有在没有线段覆盖了k次或以上的时候,才需要考虑是否有点被覆盖了k次或以上。

因为如果线段包含点的话,点就不需要独立输出了。


#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#define maxn 1000007
using namespace std;
//离散化部分 
int Rank[maxn<<1],Rn;
void SetRank(){//排序+去除重复元素
	sort(Rank+1,Rank+1+Rn);
	int I=1;
	for(int i=2;i<=Rn;++i) if(Rank[i]!=Rank[i-1]) Rank[++I]=Rank[i];
	Rn=I;
}
int GetRank(int x){//得到某个元素离散化后的下标
	int L=1,R=Rn,M;
	while(L^R){
		M=(L+R)>>1;
		if(Rank[M]<x) L=M+1;
		else R=M;
	} 
	return L;
}
//非递归线段树 
int N; 
int Add[maxn<<3];//区间覆盖次数 
int P[maxn<<3];//点覆盖次数 
void Build(int n){//建树 
	N=1;while(N < n+2) N <<= 1;
	memset(Add,0,sizeof(Add));
	memset(P,0,sizeof(P));
}
void Update(int L,int R){//区间更新
	//线段更新 
	for(int s=N+L-1,t=N+R;s^t^1;s>>=1,t>>=1){
		if(~s&1) ++Add[s^1];
		if( t&1) ++Add[t^1]; 
	}
	//点更新
	for(int s=N+L-1,t=N+R+1;s^t^1;s>>=1,t>>=1){
		if(~s&1) ++P[s^1];
		if( t&1) ++P[t^1]; 
	}
}
void PushDown(){//下推所有标记
	for(int i=1;i<N;++i){
		Add[i<<1]+=Add[i];
		Add[i<<1|1]+=Add[i];
		P[i<<1]+=P[i];
		P[i<<1|1]+=P[i];
	}
}
int n,k,l[maxn],r[maxn];
int main(void)
{
	while(~scanf("%d%d",&n,&k)){
		//输入 
		for(int i=Rn=0;i<n;++i){
			scanf("%d%d",&l[i],&r[i]);
			Rank[++Rn]=l[i];
			Rank[++Rn]=r[i];
		}
		//离散化 
		SetRank();
		//建树 
		Build(Rn);
		//更新树 
		for(int i=0;i<n;++i)
			Update(GetRank(l[i]),GetRank(r[i]));
		//下推标记 
		PushDown();
		//计算答案 
		int On=0,I=0;
		for(int i=1;i<=Rn;++i){//扫描覆盖情况
			if(Add[N+i]>=k){
				if(!On){//碰到线段的左端点,记录 
					l[++I]=Rank[i];
					On=1;
				}
			}
			else{
				if(On){//碰到线段的右端点,记录 
					r[I]=Rank[i];
					On=0;
				}
				else{//只有在不被线段覆盖时,才会考虑点是否被覆盖了k次或以上 
					if(P[N+i]>=k){//遇到被覆盖k次或以上的点,记录 
						l[++I]=Rank[i];
						r[I]=Rank[i];
					}
				}
			}
		}
		//输出结果 
		printf("%d\n",I);
		for(int i=1;i<=I;++i){
			printf("%d %d\n",l[i],r[i]);
		}
	}
return 0;
}




这道题是一道博弈论题目,需要使用到 SG 函数。SG 函数是一个函数,它的输入是一个状态,输出是一个非负整数,用来表示当前状态的取胜情况。具体而言,若 SG 函数的输出为 $0$,则当前状态必败;若 SG 函数的输出不为 $0$,则当前状态必胜。 对于这道题目,我们需要先了解一下如何通过数学分析求出 SG 函数。首先,我们需要对游戏的状态进行编码。对于本题,可以将状态表示为一个三元组 $(n, m, k)$,表示当前棋盘的大小为 $n \times m$,每个人每次最多可以取 $k$ 个棋子。接下来,我们需要定义一个从状态到状态集合的映射 $f(\cdot)$,表示从当前状态可以转移到哪些状态。对于本题,$f((n, m, k))$ 中的每个状态可以通过一次操作得到,即将当前棋盘中的某一行或某一列中的 $k$ 个棋子全部取走,然后将其剩余的部分作为新的棋盘状态。注意,对于这个映射,我们只需要考虑下一步能够到达的状态,而不需要考虑更远的状态。 接下来,我们需要定义 SG 函数的递归式。对于一个状态 $(n, m, k)$,其 SG 值可以通过其可以转移到的状态的 SG 值来计算。具体而言,我们可以将当前状态转移到的所有状态的 SG 值进行异或运算,并将结果加上 $1$,即 $SG((n, m, k)) = \text{mex}\{ SG(f((n, m, k))) \} + 1$,其中 $\text{mex}$ 表示集合中未出现的最小非负整数。 最后,我们需要解决的问题就是如何计算 $\text{mex}$ 函数。对于本题,可以使用一个 $O(k)$ 的算法来计算 $\text{mex}$。具体而言,我们可以记录所有出现的 SG 值,然后从 $0$ 开始枚举所有可能的非负整数,找到第一个未出现的整数即为 $\text{mex}$。 下面是 AC 代码:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值