CSU 1913: 送礼物 (二分答案 + 线段树)

1913: 一条龙送礼物

        Time Limit: 2 Sec     Memory Limit: 128 Mb     Submitted: 12     Solved: 7    


Description

这天一条龙(出题人dota2游戏名称)的好朋友高素质玩家(某素质玩家dota2游戏名称)天天被朋友黑,一条龙觉得太可怜了,一条龙准备送点礼物给他作为安慰。一条龙现在有一个长度为n的整数序列。
然而他的这个好朋友非常喜欢鸽子,他有想对这个序列进行某种变形操作(第一类操作),给出一些区间[l,r],要求把区间[l, r]的数字变成飞翔的鸽子的翅膀形状(如下图),进行重新排列。

即是说将数字从大到小依次放在鸽子的左右翅膀上面,然后形成一个新的序列。
不仅如此,由于这个朋友还是一个善变的人,在进行上述操作的过程中也可能进行另外一种操作(第二类操作),给出一个数p,将序列变回p个第一类操作之前的状态。 两种操作总共进行m次后,他只挑走这个序列的第k个数给拿走当作自己的礼物。
一条龙想知道最后被拿走的这个数的值是多少。

Input

第1行:n ,m,k (1<=n,m,k<=5e4)
第2行:n个数,每个数a[i],表示初始序列
1<=a[i]<=2e5
第3~m+2行:
如果是第一类操作,有三个数:1 L R ,表示对区间[L,R]进行上述操作
否则是第二类操作,有两个数:2 p ,撤销最近的p个第一类操作,如果p比第一类操作数的数量还大,那么将序列恢复成最原始的状态。
上述操作中
1<=L<=R<=n
1<=p<=m

Output

一个数,被拿走的值

Sample Input

8 10 4
15 4 12 3 14 14 5 2
2 4
1 4 5
1 6 6
2 1
1 5 8
1 4 5
1 1 3
1 1 7
1 3 8
2 1

Sample Output

2

Hint

Source

Author

HNU



        不得不说,湖大出得一手好题,出现了一次两题场……

        数学什么的、fwt什么的简直被玩坏……最后发现有些题目还可以水过,对就是这题,湖大大佬O(N^2)水过了……

        回过头来看这题,这个思想确实非常的巧妙。题目询问的是在经过一系列操作变化之后,第k个数字是多少,那么我们二分这个第k个数的数值。注意到,如果固定了最后第k个数字,那么其他的数字就能够分为两类,即比它大的和比它小的。当变换到最后,第k位比枚举的数字小,那么说明枚举大了;否则说明枚举小了。也就是说,我们只关心最后第k位的数值与枚举的数字的大小关系,那么我们就可以一开始就忽略掉多余信息,把所有比枚举的数字大的数字都变成1,其余变成0。

        可能你还没有反应过来这样子做的好处在哪。但是,你很快就发现,你已经不用排序了,更进一步说,一个区间已经可以被分为三段了。对于一个区间,进行完操作之后,它的前后两部分肯定为1,中间部分为0(根据重排规则大的数字放在两边)。这样的话,我们就可以用线段树解决这个问题,区间赋值,O(logN)的时间复杂度。至于细节方面的话,我们要确定比所枚举的数字大的数字个数才能把区间分段。很显然,维护一个区间和即可,这就是选择0和1而不选择其他数字的原因。有了这些,分成三段,区间赋值即可,最后判断第k位是1还是0。

        可以说,思路非常的巧妙,机智的丢掉一些无关的细节,让原本复杂的题目可以得到简化,值得借鉴。具体代码如下(为了强化线段树,我们没有用模板):

#include<bits/stdc++.h>
#define MAXN 100100
using namespace std;

struct node
{
	int l,r,sum,lazy;
} tree[4*MAXN];

int n,m,k,opcnt,a[MAXN];
int q[MAXN][2];
int L,R,MID;

inline void build(int i,int l,int r)
{
	tree[i].l=l;
	tree[i].r=r;
	tree[i].sum=0;
	tree[i].lazy=-1;
	if (l==r)
	{
		tree[i].sum=(a[l]>MID);
		return;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;
}

inline void push_down(int i)
{
	if (tree[i].lazy==-1) return;
	int lazy=tree[i].lazy; tree[i].lazy=-1;
	if (tree[i].l==tree[i].r) return;
	tree[i<<1].lazy=tree[i<<1|1].lazy=lazy;
	tree[i<<1].sum=(tree[i<<1].r-tree[i<<1].l+1)*lazy;
	tree[i<<1|1].sum=(tree[i<<1|1].r-tree[i<<1|1].l+1)*lazy;
	tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;
}

inline void update(int i,int l,int r,int x)
{
	if (l>r) return;
	if (tree[i].l==l && tree[i].r==r)
	{
		tree[i].sum=(r-l+1)*x;
		tree[i].lazy=x;return;
	}
	push_down(i);
	int mid=(tree[i].l+tree[i].r)>>1;
	if (r<=mid) {update(i<<1,l,r,x); push_down(i<<1|1);}
	else if (l>mid) {update(i<<1|1,l,r,x); push_down(i<<1);}
	else 
	{
		update(i<<1,l,mid,x);
		update(i<<1|1,mid+1,r,x);
	}
	tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;
}

inline int getsum(int i,int l,int r)
{
	if (l>r) return 0;
	if (l==tree[i].l && tree[i].r==r) return tree[i].sum;
	push_down(i);
	int mid=(tree[i].l+tree[i].r)>>1;
	if (r<=mid) return getsum(i<<1,l,r);
	else if (l>mid) return getsum(i<<1|1,l,r);
	else return getsum(i<<1,l,mid)+getsum(i<<1|1,mid+1,r);
}

inline void solve(int i,int l,int r)
{
	int sum=getsum(1,l,r);
	int half1=(sum+1)/2;
	int half2=sum-half1;
	update(1,l,l+half1-1,1);
	update(1,r-half2+1,r,1);
	update(1,l+half1,r-half2,0);
}

inline bool check()
{
	build(1,1,n);
	for(int i=1;i<=opcnt;i++)
		solve(1,q[i][0],q[i][1]);
	return getsum(1,k,k)==1;
}

int main()
{
	while(~scanf("%d%d%d",&n,&m,&k))
	{
		int big=-1e9,sma=1e9;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			big=max(big,a[i]);
			sma=min(sma,a[i]);
		}
		opcnt=0;
		for(int i=1;i<=m;i++)
		{
			int x; scanf("%d",&x);
			if (x==1)
			{
				opcnt++;
				scanf("%d%d",&q[opcnt][0],&q[opcnt][1]);
			} else 
			{
				int y;scanf("%d",&y);
				opcnt=max(opcnt-y,0);
			}
		}
		L=sma; R=big;
		while(L<R)
		{
			MID=(L+R)>>1;
			if (check()) L=MID+1;
			        else R=MID-1;
		}
		while (!check()) MID--;
		while (check()) MID++;		//个人写二分的保险方法……
		printf("%d\n",MID);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值