题解:P3569 [POI2014] KAR-Cards

题意

nnn 个元素,第 iii 个元素有两个权值 aia_iaibib_ibi;有 mmm 次操作,每次操作会交换两个元素的位置,且都需要回答:是否存在一种方案,使得每个元素各选择一个权值后,组成的序列从左到右单调不降。

解法

完全可以把交换操作看作两次单点修改,每次只需要考虑一个元素的变化对答案的影响即可。对于一个区间中的元素,显然开头的数越小,该区间能够单调不降的概率越大。不妨设 ai<bia_i<b_iai<bi,考虑线段树,对于区间 [L,R][L,R][L,R] 我们维护两个值:

  • LLL 个元素选择 aLa_LaL,第 RRR 个元素尽量小的情况下,第 RRR 个元素会选择 aRa_RaRbRb_RbR 或无解;
  • LLL 个元素选择 bLb_LbL,第 RRR 个元素尽量小的情况下,第 RRR 个元素会选择 aRa_RaRbRb_RbR​ 或无解。

合并两个相邻区间的信息时,我们枚举左区间 [L,R][L,R][L,R] 选择 aLa_LaLbLb_LbL、右区间 [L′,R′][L',R'][L,R] 选择 aL′a_{L'}aLbL′b_{L'}bL,判断 aR<bL′a_R<b_{L'}aR<bL 是否成立(已经保证小区间内单调不降或无解);最后贪心地让合并后的区间 [L,R′][L,R'][L,R] 末尾选择的数尽可能小。总时间复杂度 O(n+mlog⁡n)O(n+m\log n)O(n+mlogn)

实现

开两个数组 x,yx,yx,y 维护这两个值(直接存末尾卡牌选择的值)。在建树、修改的时候用子区间信息进行更新。令该区间为 [l,r][l,r][l,r],两个子区间的编号为 L,RL,RL,R(以 mid=⌊l+r2⌋mid=\lfloor\cfrac{l+r}{2}\rfloormid=2l+r 为界),我们枚举用 x[L]x[L]x[L] 还是 y[L]y[L]y[L] 连接两个子区间、用 x[R]x[R]x[R] 还是 y[R]y[R]y[R] 作为结尾,并且优先考虑使用较小的 x[R]x[R]x[R] 作为结尾。我们在修改的时候顺便更换 a,ba,ba,b 数组,则只需判断 x[L]x[L]x[L] 或者 y[L]y[L]y[L] 是否不大于 a[mid+1]a[mid+1]a[mid+1] 或者 b[mid+1]b[mid+1]b[mid+1](共 444 种)。最终该区间的末尾取决于 RRR 子区间末尾的选择。注意处理好无解的情况。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
struct Card { int A,B; } a[maxn];
int x[maxn << 2], y[maxn << 2]; // x 第一张小,y 第一张大
#define lson l,mid,rt << 1
#define rson mid + 1,r,rt << 1 | 1
const int inf = 1e7 + 5;
void update(int l,int r,int rt) {
    int mid = l + r >> 1;
    int L = rt << 1, R = rt << 1 | 1; // 两个子区间的编号
    x[rt] = y[rt] = inf;
    // 分类讨论
    if (x[L] <= a[mid + 1].A) x[rt] = x[R];
    else if (x[L] <= a[mid + 1].B) x[rt] = y[R];
    if (y[L] <= a[mid + 1].A) y[rt] = x[R];
    else if (y[L] <= a[mid + 1].B) y[rt] = y[R];
}
void build(int l,int r,int rt) {
    if (l == r) return x[rt] = a[l].A, y[rt] = a[l].B, void(0);
    int mid = l + r >> 1;
    build(lson); build(rson); update(l,r,rt);
}
int now;
void modify(int l,int r,int rt,Card k) {
    if (l == r) return x[rt] = k.A, y[rt] = k.B, void(0);
    int mid = l + r >> 1;
    if (now <= mid) modify(lson,k);
    else modify(rson,k);
    update(l,r,rt);
}
int n,m;
int main() {
    scanf("%d",&n);
    for (int i = 1;i <= n;i ++) {
        scanf("%d%d",&a[i].A,&a[i].B);
        if (a[i].A > a[i].B)
            swap(a[i].A,a[i].B);
    }
    build(1,n,1);
    scanf("%d",&m);
    for (int i = 1,u,v;i <= m;i ++) {
        scanf("%d%d",&u,&v);
        swap(a[u],a[v]);
        now = u; modify(1,n,1,a[u]);
        now = v; modify(1,n,1,a[v]);
        puts(x[1] < inf || y[1] < inf ? "TAK" : "NIE");
    }
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值