BZOJ3526[Poi2014] Card
Description
有n张卡片在桌上一字排开,每张卡片上有两个数,第i张卡片上,正面的数为a[i],反面的数为b[i]。现在,有m个熊孩子来破坏你的卡片了!
第i个熊孩子会交换c[i]和d[i]两个位置上的卡片。
每个熊孩子捣乱后,你都需要判断,通过任意翻转卡片(把正面变为反面或把反面变成正面,但不能改变卡片的位置),能否让卡片正面上的数从左到右单调不降。
Input
第一行一个n。
接下来n行,每行两个数a[i],b[i]。
接下来一行一个m。
接下来m行,每行两个数c[i],d[i]。
Output
m行,每行对应一个答案。如果能成功,输出TAK,否则输出NIE。
Sample Input
4
2 5
3 4
6 3
2 7
2
3 4
1 3
Sample Output
NIE
TAK
HINT
【样例解释】
交换3和4后,卡片序列为(2,5) (3,4) (2,7) (6,3),不能成功。
交换1和3后,卡片序列为(2,7) (3,4) (2,5) (6,3),翻转第3张卡片,卡片的正面为2,3,5,6,可以成功。
【数据范围】
n≤200000,m≤1000000,0≤a[i],b[i]≤10000000,1≤c[i],d[i]≤n.
Solution:
果然我还是太弱了点…
这道题就是典型的在线段树上分治处理询问呀…
在线段树上存 mi,mx 表示该左端点取小值和大值时(卡牌的两面)右端点的最小值,没有就是-1。
合并区间的话就很简单了,判断右区间的左端点和左区间的值的大小即可。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#define M 200005
using namespace std;
inline void Rd(int &res){
char c;res=0;
while(c=getchar(),c<'0');
do{
res=(res<<1)+(res<<3)+(c^48);
}while(c=getchar(),c>='0');
}
int A[M],B[M];
struct SegTree{
struct Node{int mx,mi;}Tree[M<<2];
void Up(int p,int pos){
if(Tree[p<<1].mi==-1)Tree[p].mi=-1;
else if(A[pos]>=Tree[p<<1].mi)Tree[p].mi=Tree[p<<1|1].mi;
else if(B[pos]>=Tree[p<<1].mi)Tree[p].mi=Tree[p<<1|1].mx;
else Tree[p].mi=-1;
if(Tree[p<<1].mx==-1)Tree[p].mx=-1;
else if(A[pos]>=Tree[p<<1].mx)Tree[p].mx=Tree[p<<1|1].mi;
else if(B[pos]>=Tree[p<<1].mx)Tree[p].mx=Tree[p<<1|1].mx;
else Tree[p].mx=-1;
}
void Build(int L,int R,int p){
if(L==R){
Tree[p].mi=A[L];
Tree[p].mx=B[L];
return;
}
int mid=(L+R)>>1;
Build(L,mid,p<<1);
Build(mid+1,R,p<<1|1);
Up(p,mid+1);
}
void Update(int L,int R,int x,int p){
if(L==R){
Tree[p].mi=A[L];
Tree[p].mx=B[L];
return;
}
int mid=(L+R)>>1;
if(x<=mid)Update(L,mid,x,p<<1);
else Update(mid+1,R,x,p<<1|1);
Up(p,mid+1);
}
}SegTree;
int main(){
int n,m;
Rd(n);
for(int i=1;i<=n;i++){
Rd(A[i]),Rd(B[i]);
if(A[i]>B[i])swap(A[i],B[i]);
}
SegTree.Build(1,n,1);
Rd(m);
while(m--){
int a,b;
Rd(a),Rd(b);
swap(A[a],A[b]);
swap(B[a],B[b]);
SegTree.Update(1,n,a,1);
SegTree.Update(1,n,b,1);
if(~SegTree.Tree[1].mi)puts("TAK");
else puts("NIE");
}
return 0;
}