P5522 [yLOI2019] 棠梨煎雪 题解

题意

给定 m m m 个字符串,每个字符串仅含 0/1/? \texttt{0/1/?} 0/1/?,长度均为 n n n 0 \texttt{0} 0 1 \texttt{1} 1 分别可以和自己匹配, ? \texttt{?} ? 可以匹配 0 \texttt{0} 0 1 \texttt{1} 1

定义一个 0/1 \texttt{0/1} 0/1 串可以匹配一个仅含 0/1/? \texttt{0/1/?} 0/1/? 的串当且仅当它们长度相同且对应位置均可匹配。

q q q 次询问,每次进行一种操作:

  1. 修改第 i i i 个字符串为另一个仅含 0/1/? \texttt{0/1/?} 0/1/? 的长度为 n n n 的串;
  2. 给定 [ l , r ] [l,r] [l,r],查询有多少 0/1 \texttt{0/1} 0/1 串可以匹配到第 l l l 个到第 r r r 个串全体。

思路

考虑对于一段所有字符串的第 x x x 个字符,一共有四种可能:确定为 0 \texttt{0} 0;确定为 1 \texttt{1} 1;都可以;都不可以。如果 0/1 \texttt{0/1} 0/1 都不可以则答案为 0 0 0,因为不会有任何一个字符串匹配该区间。如果确定为某个数,则这一位只有一种可能;否则这一位有两种可能。根据乘法原理,如果有 a a a 个位置有两种可能,则本次询问的答案为 2 a 2^a 2a

注意到字符串长度只有 30 30 30,所以一个串每个位置的信息可以用一个 pair<int,int> 存下来:

  • first 的第 i i i 位为 0 0 0 表示第 i i i 个字符可以是 0 \texttt0 0 也可以是 1 \texttt1 1
  • first 的第 i i i 位为 1 1 1 表示第 i i i 个字符是 second 的第 i i i 位。

这个信息可以推广到区间上: [ l , r ] [l, r] [l,r] 内每个位置的信息也可以用这样一个 pair<pair<int, int>, bool> 存下来:

  • first.first 的第 i i i 位为 0 0 0 表示这个区间里所有的串第 i i i 个字符可以是 0 \texttt0 0
    可以是 1 \texttt1 1
  • first.second 的第 i i i 位为 1 1 1 表示这个区间里所有串的第 i i i 个字符是 second
    的第 i i i 位。

second 0 0 0 表示这个区间的信息不合法。转移时从左右孩子进行一下位运算合并。如果两个孩子已确定的
位置有冲突则区间不合法。具体的实现见代码。

Code

// 这道题是个区间询问,单点修改,所以有兴趣的读者可以去看看树状数组的题解(本人蒟蒻不会
#include<bits/stdc++.h>
#define lson l,mid,rt << 1
#define rson mid + 1,r,rt << 1 | 1
#define mk(x,y,z) make_pair(make_pair(x,y),z)
#define type pair<pair<int,int>,bool>
using namespace std;
const int maxn = 1e5 + 5;
type sg[maxn << 4]; int x[maxn],y[maxn]; // sg为线段树,x,y分别存储原串的?位 & 原串的0/1位
char s[maxn],k[maxn]; int nowl,nowr,n,m,q; // nowl和nowr表示查询区间为[nowl,nowr]
void update(int rt) {
    int ls = rt << 1, rs = rt << 1 | 1;
    sg[rt].first.first = sg[ls].first.first | sg[rs].first.first,
    // 当前区间的自由位,为左右子区间同时为自由位的位
    sg[rt].first.second = sg[ls].first.second | sg[rs].first.second,
    // 当前区间的固定位,为左右子区间同一位上至少有一个固定位的位
    sg[rt].second = sg[ls].second & sg[rs].second & !((sg[ls].first.first & sg[rs].first.first) & (sg[ls].first.second ^ sg[rs].first.second));
    // 答案为0的情况:
    // 1. 左右孩子本身答案就为0
    // 2. 左右孩子在固定位上产生冲突
}
void build(int l,int r,int rt) {
    if (l == r) {
        sg[rt] = mk(x[l],y[l],true); // 自己总是可以匹配自己
        return ;
    }
    int mid = (l + r) >> 1;
    build(lson); build(rson); update(rt);
    // cout << sg[rt]
}
type query(int l,int r,int rt) {
    if (nowl <= l && r <= nowr) return sg[rt];
    int mid = (l + r) >> 1; 
    if (nowl <= mid && mid < nowr) {
        type ls = query(lson), rs = query(rson), res;
        res.first.first = ls.first.first | rs.first.first,
        res.first.second = ls.first.second | rs.first.second,
        res.second = ls.second & rs.second & !((ls.first.first & rs.first.first) & (ls.first.second ^ rs.first.second));
        // 返回值与update计算原理相同
        return res;
    }
    if (nowl <= mid) return query(lson);
    if (mid < nowr) return query(rson);
}
void modify(int l,int r,int rt,int u,int v) { // 用nowl记录修改位置
    if (l == r) {
        sg[rt] = mk(u,v,true);
        return ;
    }
    int mid = (l + r) >> 1;
    if (nowl <= mid) modify(lson,u,v);
    else modify(rson,u,v);
    update(rt);
}
int main() {
    scanf("%d%d%d",&n,&m,&q);
    for (int i = 1;i <= m;i ++) {
        scanf("%s",s); // 当第i位为自由位的时候我们令第i位固定位为0,方便处理
        for (int j = 0;j < n;j ++)
            if (s[j] != '?') // 固定位
                x[i] |= 1 << (n - j), 
                y[i] |= (s[j] - '0') << (n - j); 
    }
    build(1,m,1); int ans = 0;
    for (int t = 1,op,u,v,now;t <= q;t ++) {
        scanf("%d",&op);
        if (op) { // 题意中的操作一
            scanf("%d %s",&nowl,k), u = v = 0;
            for (int j = 0;j < n;j ++) // 按照最开始的方法计算出修改值串的信息
                if (k[j] != '?')
                    u |= 1 << (n - j),
                    v |= (k[j] - '0') << (n - j);
            modify(1,m,1,u,v);
        } else { // 提议中的操作二
            scanf("%d%d",&nowl,&nowr), now = 1;
            type res = query(1,m,1);
            if (!res.second) { // 答案为0
                ans ^= 0;
                continue;
            }
            for (int i = 1;i <= n;i ++) // 按照乘法原理计算个数
                if (!(res.first.first & (1 << (i))))
                    now <<= 1;
            ans ^= now;
        }
    }
    printf("%d",ans);
    return 0;
}
  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值