NKOJ-3777 卡牌操作

来源:互联网 发布:湖南网络公关公司 编辑:程序博客网 时间:2024/05/22 17:00

P3777卡牌操作
时间限制 : - MS 空间限制 : 165536 KB
评测说明 : 1000ms
问题描述

有n张卡片在桌上一字排开,每张卡片上有两个数,第i张卡片上,正面的数为a[i],反面的数为b[i]。现在,有m个熊孩子来破坏你的卡片了!第i个熊孩子会交换c[i]和d[i]两个位置上的卡片。每个熊孩子捣乱后,你都需要判断,通过任意翻转卡片(把正面变为反面或把反面变成正面,但不能改变卡片的位置),能否让卡片正面上的数从左到右单调不降。

输入格式

第一行一个n。接下来n行,每行两个数a[i],b[i]。接下来一行一个m。接下来m行,每行两个数c[i],d[i]。

输出格式

m行,每行对应一个答案。如果能成功,输出TAK,否则输出NIE。

样例输入

42 53 46 32 723 41 3

样例输出

NIETAK

提示

【样例解释】交换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.

来源 POI 2014

最好奇的是这种牌有什么用…

题解

像这么大的数据,还要多次询问,那么每一次的操作时间复杂度绝对不超过log n
那顺着这个思路走下去,对于一段区间,进行时间复杂度为log n的操作
那只有可能是——二分

那么怎么进行二分呢?

对于一段牌[l,r]是否能成为一段单调不递增区间 首先能够得到它要满足的最简单的条件就是
[l,mid] 和 [mid+1,r]一定首先可以成为单调不递增区间
再其次才是 这两段区间能否合并成为一个单调不递增区间

所以说 我们就想到了一个可能的操作——不停二分,直到区间长度变成1

当区间长度变为1时,那么它自身自然就是一个单调不递增序列

那么如何对两段区间合并之后的大区间能否成为一个单调不递增区间进行讨论呢?

前面都是瞎扯 解题现在开始

那么我们首先就要确定一些状态了

首先对于一张牌只有一种值的情况进行讨论

对于两段区间[l,mid],[mid+1,r]能否合并成一个区间,我们肯定需要以下几个参数

f[l,r] ⇒  [l,r]能否形成单调不递减区间num[x] ⇒ x点的值

那么简单分析就能得到合并条件

①f[l,mid]==True ②f[mid+1,r]==True③num[mid]<=num[mid+1]

如此一来就可以确定两段区间是否合并成一个单调不递减序列(必须全部满足)

推广到一个点有两个值的情况

由于一段区间能否成为一个单调不递减序列与这段区间的开头值与结尾值相关
而每一段区间的开头和结尾都有了两种可能(不算不成立的情况)
那么我们可能就需要多一些参数了

Z[l,r] ⇒  第一张牌为正面时的最后一张牌的值F[l,r] ⇒  第一张牌为反面时的最后一张牌的值numz[x] ⇒ x点的正面值numf[x] ⇒ x点的负面值

其中 当[l,r]不能以第一张牌为正面作为开头时 Z[l,r]=-1
F[l,r]=-1的情况同理

可以先停下来思考一下为什么要存Z和F

于是对于这种两面牌的单调不递减序列 就有了判定方法

首先判定第一张牌为正面的情况①Z[l,mid]!=-1②Z[mid+1,r]!=-1③Z[l,mid]<=numz[mid+1]④F[mid+1,r]!=-1⑤Z[l,mid]<=numf[mid+1]其中 ①②③ 成立 或 ①④⑤ 成立即可更新(①②③成立 Z[l,r]=Z[mid+1,r]  ①④⑤成立 Z[l,r]=F[mid+1,r])  若两者同时成立 则取更小值

而这种更新的操作方法就是线段树(点修改)

提示

强行使numz[x]<=numf[x]可以简化部分操作
顺便偷个懒 有一些细节我就不细说了

附上对拍代码

#include <iostream>#include <cstdio>#define ls (ori<<1)#define rs (ori<<1|1)#define mid (l+r>>1)using namespace std;int z[201234],f[201234],x;int tailz[1601234],tailf[1601234];inline int input(){    char c=getchar();int o;    while(c>57||c<48)c=getchar();    for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;    return o;}void flash(int l,int r,int ori)//更新操作{    if(tailz[ls]==-1)tailz[ori]=-1;    else    {        if(tailz[rs]!=-1&&tailz[ls]<=z[mid+1])tailz[ori]=tailz[rs];        else if(tailf[rs]!=-1&&tailz[ls]<=f[mid+1])tailz[ori]=tailf[rs];        else tailz[ori]=-1;    }    if(tailf[ls]==-1)tailf[ori]=-1;    else    {        if(tailz[rs]!=-1&&tailf[ls]<=z[mid+1])tailf[ori]=tailz[rs];        else if(tailf[rs]!=-1&&tailf[ls]<=f[mid+1])tailf[ori]=tailf[rs];        else tailf[ori]=-1;    }}void BT(int l,int r,int ori){    if(l==r)tailz[ori]=z[l],tailf[ori]=f[l];//当区间长度为1时的操作    else    {        BT(l,mid,ls),BT(mid+1,r,rs);        flash(l,r,ori);    }}void UD(int l,int r,int ori){    if(l==r)tailz[ori]=z[l],tailf[ori]=f[l];    else    {        //细分区间直到找到修改的那个点(区间长度为1)为止        if(x<=mid)UD(l,mid,ls);        else UD(mid+1,r,rs);        flash(l,r,ori);    }}int main(){//  freopen("In.txt","r",stdin);    int n=input(),m,a,b;    for(int i=1;i<=n;i++)    {        a=input();b=input();        if(a>b)z[i]=b,f[i]=a;        else z[i]=a,f[i]=b;    }    BT(1,n,1);    m=input();    for(int i=1;i<=m;i++)    {        a=input();b=input();        swap(z[a],z[b]);        swap(f[a],f[b]);        x=a;UD(1,n,1);        x=b;UD(1,n,1);        if(tailz[1]==-1&&tailf[1]==-1)puts("NIE");        else puts("TAK");    }}