洛谷題單指南-線段樹-P5522 [yLOI2019] 棠梨煎雪

五月江城發表於2024-12-11

原題連結:https://www.luogu.com.cn/problem/P5522

題意解讀:有若干0/1/?組成的字串,支援兩種操作:1.將制定位置字串修改成新字串;2.查詢區間內字串能否統一成一個字串,求有多少種可能;將2的所有結果異或起來,再和0異或,輸出最終答案。注意:?表示可以用0或1取代。

解題思路:單點修改,區間查詢,可以用線段樹解決。

關鍵看線段樹節點維護什麼資訊?

字串要能統一成一個,對應位置字元必須符合:1.都是'1' 2.都是'0' 3.有一個是'?'

對於1/2是固定的,對於3,如果一個是'?'一個是非'?'那麼情況也是唯一的,如果兩個都是'?'那麼既可以都是'1'又可以都是'0',這樣就會有2中可能

因此對於兩個字串能統一成多少種相同的串,就是要找到有多少個位置都是'?',設有x個,則一共有2^x中可能。

如果直接維護字串,在進行pushup等操作的時候每次常數都比較高,需要一種更最佳化的方式,如果將對應位置的值對映到二進位制就好處理了。

方式如下:

設v1是一個整數,其二進位制表示一個01?字串中確定是'1'的位置為1,如0?1,第2個位置是'1',那麼v1二進位制為100,v1=4

設v2是一個整數,其二進位制表示一個01?字串中確定是'0'的位置為1,如0?1,第0個位置是'0',那麼v2二進位制為001,v2=1

有了以上定義,

1、在進行節點合併時,root.v1 = left.v1 | right.v1,root.v2 = left.v2 | right.v2

2、在進行區間查詢時,直接返回合併後的結果即可,再進一步進行判斷和統計?的個數,

- 判斷邏輯:如果v1 & v2不為0,說明多個字串相同位置既有'1'又有'0',不可能統一成一種,結果是0

- 統計方式:如果v1 & v2為0,將v1 | v2,統計其中字串長度範圍內0的個數x,即表示'?'的個數,再取2^x,即表示可以統一成多少種字串

3、單點修改操作就是常規的修改,注意將字串轉化成v1、v2

具體細節參考程式碼。

100分程式碼:

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;

struct Node
{
    int l, r;
    int v1; //v1的二進位制表示中,[l,r]區間內所有字串位置i為'1'才是1
    int v2; //v1的二進位制表示中,[l,r]區間內所有字串位置i為'0'才是1
} tr[N * 4];
string a[N];
int n, m, q, ans;

void pushup(Node &root, Node &left, Node &right)
{
    root.v1 = left.v1 | right.v1;
    root.v2 = left.v2 | right.v2;
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if(l == r)
    {
        for(int i = 0; i < a[l].length(); i++)
        {
            if(a[l][i] == '1') tr[u].v1 |= (1 << i);
            else if(a[l][i] == '0') tr[u].v2 |= (1 << i);
        }
    }
    else
    {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

Node query(int u, int l, int r)
{
    if(tr[u].l >= l && tr[u].r <= r) return tr[u];
    else if(tr[u].l > r || tr[u].r < l) return Node{};
    else
    {
        Node res = {};
        Node left = query(u << 1, l, r);
        Node right = query(u << 1 | 1, l, r);
        pushup(res, left, right);
        return res;
    }
}

void update(int u, int pos, string t)
{
    if(tr[u].l == tr[u].r) 
    {
        tr[u].v1 = tr[u].v2 = 0; //先清空
        for(int i = 0; i < t.length(); i++)
        {
            if(t[i] == '1') tr[u].v1 |= (1 << i);
            else if(t[i] == '0') tr[u].v2 |= (1 << i);
        }
    }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if(pos <= mid) update(u << 1, pos, t);
        else update(u << 1 | 1, pos, t);
        pushup(u);
    }
}

int main()
{
    cin >> n >> m >> q;
    for(int i = 1; i <= m; i++) cin >> a[i];
    build(1, 1, m);
    int op, l, r, pos;
    string t;
    while(q--)
    {
        cin >> op;
        if(op == 0)
        {
            cin >> l >> r;
            Node res = query(1, l, r);
            if(res.v1 & res.v2) //說明有'1'的位置也有'0',不能統一成一個字串
            {
                ans ^= 0; //結果是0
            }
            else
            {
                int x = res.v1 | res.v2;
                int cnt = 0;
                for(int i = 0; i < n; i++)
                {
                    if(x >> i & 1) cnt++; //統計確定是'1'或'0'的位置數量
                }
                int ques = n - cnt; //'?'的個數
                ans ^= (1 << ques); // 2^ques即有多少種字串組合
            }
        }
        else
        {
            cin >> pos >> t;
            update(1, pos, t);
        }
    }
    cout << ans;
    return 0;
}

相關文章