P3569 [POI2014] KAR-Cards

发布时间 2023-07-12 19:18:00作者: 朝绾曦梦

题目链接:P3569 [POI2014] KAR-Cards

来自同机房大佬 @L_ndyz 的奇特想法。

首先,这道题目询问能否构成一个不下降序列,这显然可以设计一个状态转移方程,如果符合不下降,就给数组加上一,这实际上是在统计转移的次数,最终只需要询问下标为 \(n\) 的数组,若小于 \(n\) 说明无法满足整个序列都满足不下降。

这题又添加了一张卡牌有正反面,就需要分情况了。

定义 \(f\) 数组第一维存牌的标号,第二维存正面或者反面朝上。

代码如下:

    f[i][0] = max(f[i - 1][0] + (a[i] >= a[i-1]), f[i - 1][0] + (a[i] >= b[i-1])); 
    f[i][1] = max(f[i - 1][1] + (b[i] >= a[i-1]), f[i - 1][1] + (b[i] >= b[i-1]));

我们发现,每张牌都只有可能被他由一张牌转移,不难想到用矩阵加速。

怎么构建矩阵呢?构建什么矩阵呢?

下面给出转移矩阵:

    for(int i = 2; i <= n; i++){
        s[i].st[0][0] = (a[i] >= a[i-1]);
        s[i].st[1][0] = (a[i] >= b[i-1]);
        s[i].st[0][1] = (b[i] >= a[i-1]);
        s[i].st[1][1] = (b[i] >= b[i-1]);
    }

然后自定义矩阵运算为取最大值,岂不是完美完成了上面的转移操作啦?

现在考虑更换两张卡片,对于转移矩阵,会对谁有影响?不难想到,只会对更换的这两张和每一张的后面一张(总共四张)卡片有影响,这些我们就重新构造一下转移矩阵就好了。然后用线段树进行计算,求值。经过这些优化(再吸个氧),上面的暴力做法就变得非常华丽啦,收工!

见代码:

#include<cmath>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
using namespace std;
struct pp{
    int st[2][2];//矩阵
    pp(){ memset(st,0,sizeof(st));}
    pp operator*(pp a){
        pp c;
        for(int i = 0;i < 2; i++){
            for(int j = 0;j < 2; j++){
                for(int k = 0;k < 2; k++){
                    c.st[i][j] = max(c.st[i][j],st[i][k] + a.st[k][j]);//广义矩阵乘法
                }
            }
        }
        return c;
    }
};
int n,m;
int a[200100],b[200100];
pp s[200100];
void change_point(int i){//改点值
    if(i == 1) return ;//初始矩阵不用改
    s[i].st[0][0] = (a[i] >= a[i - 1]); s[i].st[1][0] = (a[i] >= b[i - 1]);
    s[i].st[0][1] = (b[i] >= a[i - 1]); s[i].st[1][1] = (b[i] >= b[i - 1]);
}
struct tree{ int l,r; pp sum; }t[800100];
void build(int p,int L,int R){
    t[p].l = L;  t[p].r = R;
    if(L == R){ t[p].sum = s[L];return ;}
    int mid = (L + R) >> 1;
    build(p * 2,L,mid);build(p * 2 + 1,mid + 1,R);
    t[p].sum = t[p * 2].sum * t[p * 2 + 1].sum;
}
void change_tree(int p,int x){
    if(t[p].l == t[p].r){
        t[p].sum = s[t[p].l]; return ;
    }
    int mid = (t[p].l + t[p].r) >> 1;
    if(x <= mid) change_tree(p * 2,x);
    else change_tree(p * 2 + 1,x);
    t[p].sum = t[p*2].sum * t[p * 2 + 1].sum;
}
inline int read(){
	int x = 0,f = 1; char ch=getchar();
	while (ch < '0' || ch > '9'){if (ch == '-') f = -1;ch = getchar();}
	while (ch >= '0'&&ch <= '9'){x = x * 10 + ch - 48; ch = getchar();}
	return x * f;
}
void swap(int &x,int &y){
    int t=x; x=y; y=t;
}
signed main(){
    cin>>n;
    for(int i = 1; i <= n; i++){
        a[i] = read();b[i] = read();
    }
    s[1].st[0][0]=1;s[1].st[0][1]=1;//构造初始状态矩阵
    for(int i = 2; i <= n; i++){//根据转移方式构造每一步的矩阵
        s[i].st[0][0] = (a[i] >= a[i-1]);
        s[i].st[1][0] = (a[i] >= b[i-1]);
        s[i].st[0][1] = (b[i] >= a[i-1]);
        s[i].st[1][1] = (b[i] >= b[i-1]);
    }
    build(1,1,n);//建线段树
    cin>>m;
    for(int i = 1; i <= m; i++){
        int x, y;  x = read();  y = read();
        swap(a[x],a[y]);  swap(b[x],b[y]);
        //更换两张卡,这两张卡只会对 x,x+1,y,y+1的矩阵有影响  
        if(x != 1)     change_point(x),    change_tree(1,x);
        if(x + 1 <= n) change_point(x + 1),change_tree(1,x + 1);
        if(y != 1)     change_point(y),    change_tree(1,y);
        if(y + 1 <= n) change_point(y + 1),change_tree(1,y + 1);
        pp ans = t[1].sum;
        if(ans.st[0][0] < n && ans.st[0][1] < n) puts("NIE");//无法构成长度为 n 的不下降序列
        else puts("TAK");
    }
	return 0;
}