题解:P3569 [POI2014] KAR-Cards

题意

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

解法

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

  • L L L 个元素选择 a L a_L aL,第 R R R 个元素尽量小的情况下,第 R R R 个元素会选择 a R a_R aR b R b_R bR 或无解;
  • L L L 个元素选择 b L b_L bL,第 R R R 个元素尽量小的情况下,第 R R R 个元素会选择 a R a_R aR b R b_R bR​ 或无解。

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

实现

开两个数组 x , y x,y x,y 维护这两个值(直接存末尾卡牌选择的值)。在建树、修改的时候用子区间信息进行更新。令该区间为 [ l , r ] [l,r] [l,r],两个子区间的编号为 L , R L,R L,R(以 m i d = ⌊ l + r 2 ⌋ mid=\lfloor\cfrac{l+r}{2}\rfloor mid=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 , b a,b a,b 数组,则只需判断 x [ L ] x[L] x[L] 或者 y [ L ] y[L] y[L] 是否不大于 a [ m i d + 1 ] a[mid+1] a[mid+1] 或者 b [ m i d + 1 ] b[mid+1] b[mid+1](共 4 4 4 种)。最终该区间的末尾取决于 R R R 子区间末尾的选择。注意处理好无解的情况。

代码

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值