22014

排序HEOI2016/TJOI2016 二分+线段树判定

  LINK:排序 此题甚好我一点思路都没有要是我当时省选此题除了模拟我恐怕想不到还可以二分 还可以线段树。。。

有点ex 不太好写 考虑 暴力显然每次给出询问我们都是可以直接sort的 无视地形无视一切直接sort 。复杂度mnlogn 30分到手。

考虑更为优秀的做法 桶排序 每次排序都是O(n)总体复杂度 nm 50分到手

考虑 不模拟做题 不断排序肯定是行不通的 如何 改变Question模型是关键之处。

然鹅 很难想到解法 思路引导一下 0 1 序列线段树树是很容易支持排序的 区间修改区间查询即可。再考虑答案是否具有单调性 从数值大小来说答案虽然是确定的 但是 显然也具有单调性(虽然有点牵强

但是我们可以不断地缩小答案的 取值范围 从而寻找到答案这是关键,从答案的值域角度来说的确可以二分。想办法不断缩小答案的值域,怎么办?
考虑最终拍好序的队列>=mid 为1 反之为0 那么我们把整个操作倒序的实现其实就是0 1 的不断排序。且最后询问的位置一定为1。因为>=当前答案。

考虑如果答案较大那么经过0 1 的排序之后最后询问的位置一定不为1 所以我们成功的缩小的值域 从而达到判定mid的效果二分可行 排序01串用上述方法搞就好了。

写完了 码力最近下降很多 可能 打代码的时候 精力不够吧。

//#include<bits/stdc++.h> #include<iostream> #include<cmath> #include<ctime> #include<algorithm> #include<cctype> #include<utility> #include<queue> #include<map> #include<set> #include<bitset> #include<deque> #include<vector> #include<cstdio> #include<cstdlib> #include<iomanip> #include<stack> #include<string> #include<cstring> #define INF 1000000000 #define ll long long #define db double #define max(x,y) ((x)>(y)?(x):(y)) #define min(x,y) ((x)>(y)?(y):(x)) #define sum(x) t[x].sum #define cnt(x) t[x].cnt #define l(x) t[x].l #define r(x) t[x].r #define tag(x) t[x].tag #define op(i) s[i].op #define x(i) s[i].x #define y(i) s[i].y #define zz p<<1 #define yy p<<1|1 using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=100010; int n,m,pos,sum0,sum1; int a[MAXN],b[MAXN]; struct wy { int l,r; int sum,cnt; int tag; }t[MAXN<<2]; struct data{int op,x,y;}s[MAXN]; inline void build(int p,int l,int r) { l(p)=l;r(p)=r;tag(p)=-1; if(l==r){sum(p)=b[l];cnt(p)=b[l]^1;return;} int mid=(l+r)>>1; build(zz,l,mid); build(yy,mid+1,r); sum(p)=sum(zz)+sum(yy); cnt(p)=cnt(zz)+cnt(yy); } inline void pushdown(int p) { int mid=(l(p)+r(p))>>1; if(tag(p)==1) { sum(zz)=(mid-l(p)+1); cnt(zz)=0; sum(yy)=r(p)-mid; cnt(yy)=0; tag(zz)=tag(yy)=1; tag(p)=-1;return; } sum(zz)=0; cnt(zz)=(mid-l(p)+1); sum(yy)=0; cnt(yy)=r(p)-mid; tag(yy)=tag(zz)=2; tag(p)=-1;return; } inline void change(int p,int l,int r,int x) { if(l>r)return; if(l<=l(p)&&r>=r(p)) { if(x==1) { sum(p)=r(p)-l(p)+1; cnt(p)=0;tag(p)=1; return; } cnt(p)=r(p)-l(p)+1; sum(p)=0;tag(p)=2; return; } if(tag(p)!=-1)pushdown(p); int mid=(l(p)+r(p))>>1; if(l<=mid)change(zz,l,r,x); if(r>mid)change(yy,l,r,x); sum(p)=sum(zz)+sum(yy); cnt(p)=cnt(zz)+cnt(yy); } inline void ask(int p,int l,int r) { if(l<=l(p)&&r>=r(p)) { sum0+=cnt(p); sum1+=sum(p); return; } if(tag(p)!=-1)pushdown(p); int mid=(l(p)+r(p))>>1; if(l<=mid)ask(zz,l,r); if(r>mid)ask(yy,l,r); return; } inline int check(int x) { for(int i=1;i<=n;++i)b[i]=a[i]>=x?1:0; build(1,1,n); for(int i=1;i<=m;++i) { sum0=sum1=0; ask(1,x(i),y(i)); if(!op(i))//0升序 { //cout<<x(i)<<' '<<x(i)+sum0-1<<endl; //cout<<x(i)+sum0<<' '<<y(i)<<endl; change(1,x(i),x(i)+sum0-1,2); change(1,x(i)+sum0,y(i),1); } else { change(1,x(i),x(i)+sum1-1,1); change(1,x(i)+sum1,y(i),2); } } sum0=sum1=0; ask(1,pos,pos); return sum1; } int main() { //freopen("1.in","r",stdin); n=read();m=read(); for(int i=1;i<=n;++i)a[i]=read(); for(int i=1;i<=m;++i) { int op,x,y; op=read();x=read();y=read(); s[i]=(data){op,x,y}; } pos=read(); int l=1,r=n; while(l+1<r) { int mid=(l+r)>>1; if(check(mid))l=mid; else r=mid; } if(check(r))printf("%d\n",r); else printf("%d\n",l); return 0; } View Code

最后再次回顾本题这样做法的正确性 1 我们似乎找不到一种除了模拟之外更好的方法解决本题

2 强行考虑答案的单调性 发现答案的值域显然具有单调性 想办法缩小值域 直至找到答案。

3 缩小值域方法的正确性 对于>=答案的数字看成1的话反之为0 考虑最终的序列我们可以将其抽象成0 1 串那么最后答案所在位为1。每次排序的时候我们都是将1排到前面或排到后面 最终形成一个最终的序列,于是乎我们利用等效法确定了一个01串只要这个01串最后的排序满足答案的位置为1 那么当前mid就有可能合法然后二分不断地去找那个成功与不成功的边界成功的边界就是我们寻找的答案了,正确性可以理解为等效法显然是正确的 判断的正确性显然是单调(答案可以做到比答案小的也就是1的数量更多也能做到。故此方法正确。于是就成功的解决了这个看似解决不了的Question。

来源:博客园

作者:chdy

链接:https://www.cnblogs.com/chdy/p/11424426.html

Recommend