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

来源:互联网 发布:linux中的uniq 编辑:程序博客网 时间:2024/05/18 01:26

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 415 4 12 3 14 14 5 22 41 4 51 6 62 11 5 81 4 51 1 31 1 71 3 82 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 100100using 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;}

0 0
原创粉丝点击