卡牌操作 线段树维护区间连通性

NKOJ3777 卡牌操作

问题描述

有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。

样例输入

4
2 5
3 4
6 3
2 7
2
3 4
1 3

样例输出

NIE
TAK

提示

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


首先把每个卡片拆成两个点,不妨设其中较小的为 Ai ,较大的为 Bi 。那么如果我们把一左一右满足单调不降的两点间连一条边,那么问题就转换为了能否从1号卡片找到一条能够到达N号卡片的路径。

线段树能够处理一些区间的问题。那么可以考虑能否通过递归的原理,通过两段相邻区间的连通性得出合成的大区间的连通性。

显然我们只需要关心两段区间分别的连通性、两段区间的起止点的值。

首先一个显然的贪心,如果有多种方案使得某一区间连通,那么区间的终点值应该尽可能小。设区间中,从左端点较小值出发能够到达的右端点终点最小值为 VA ,从较大值出发的为 VB ,当前节点为p,左儿子节点为ls,右儿子节点为rs。

p节点的 VA 值将这样被得到:

(1)若ls的 VA 值小于等于rs起点的 A ,那么p节点的VA值就是rs的 VA 值;
(2)若不满足(1),但ls的 VA 值小于等于rs起点的 B ,那么p节点的VA值就是rs的 VB 值;
(3)若上述两项均不满足,显然p代表的区间不可能连通。把p的 VA 置为无穷大即可。

对于 p节点的 VB 值同理。

所以,最终的结论是:

只要根节点的 VA VB 中至少一个不是无穷大,那么就连通。 反之就不连通。

对于熊孩子的操作,单点修改处理,回溯时更新 VA VB 即可。


#include<stdio.h>
#include<algorithm>
#define MAXN 200005
#define MAXT 800005
#define inf 1e9
using namespace std;

int N,M,A[MAXN],B[MAXN];

int a[MAXT],b[MAXT],Va[MAXT],Vb[MAXT];

void Update(int p)
{
    int ls=p<<1,rs=ls|1;
    if(Va[ls]<=A[a[rs]])Va[p]=Va[rs];
    else if(Va[ls]<=B[a[rs]])Va[p]=Vb[rs];
    else Va[p]=inf;

    if(Vb[ls]<=A[a[rs]])Vb[p]=Va[rs];
    else if(Vb[ls]<=B[a[rs]])Vb[p]=Vb[rs];
    else Vb[p]=inf; 
}

void Build(int p,int x,int y)
{
    a[p]=x;b[p]=y;
    if(x==y)
    {
        Va[p]=A[x];Vb[p]=B[x];
        return;
    }
    int mid=x+y>>1;
    Build(p<<1,x,mid);
    Build(p<<1|1,mid+1,y);
    Update(p);
}

void Modify(int p,int k)
{
    if(a[p]==b[p]&&a[p]==k)
    {
        Va[p]=A[k];Vb[p]=B[k];
        return;
    }
    int mid=a[p]+b[p]>>1;
    if(k<=mid)Modify(p<<1,k);
    if(k>mid)Modify(p<<1|1,k);
    Update(p);
}

int main()
{
    int i,x,y;

    scanf("%d",&N);
    for(i=1;i<=N;i++)
    {
        scanf("%d%d",&x,&y);
        if(x>y)swap(x,y);
        A[i]=x;B[i]=y;
    }

    Build(1,1,N);

    scanf("%d",&M);
    for(i=1;i<=M;i++)
    {
        scanf("%d%d",&x,&y);
        swap(A[x],A[y]);swap(B[x],B[y]);
        Modify(1,x);
        Modify(1,y);
        if(Va[1]==inf&&Vb[1]==inf)puts("NIE");
        else puts("TAK");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值