Flower's Land 2做题笔记 && 非阿贝尔群哈希

Heiliges Requiem

神仙 hash 题。

题意简述

考虑一个 \(0/1/2\) 这三个数组成的长度为 \(n\) 的数列,\(q\) 次操作,分两种:

  • \(\forall l\leq i\leq r, a_i\leftarrow(a_i+1)\mod 3\)
  • 查询如果选出 \(l~r\) 的子序列,能否通过若干次删除相邻相同数字的方式删完这个子序列。

\(n, q\leq 5\times 10^5\)

分析

通过平凡的分析,可以得到一个性质:如果 \([l, r]\) 可行,\([l, mid]\) 可行,则 \([mid+1, r]\) 可行。

使用这个性质可以解决无修改的情况:设 \(f_i\) 表示最小的 \(r\) 使得 \([i, r]\) 可行。这个能够 \(O(n)\) 计算,然后使用倍增可以做到 \(O(\log n)\) 查询。

然后考虑如何扩展到有修改的情况下,你会发现无法扩展。

所以上面这段话对解决这道题没有任何作用。

解法

带修改的区间问题考虑线段树,因此考虑使用线段树维护哈希值。现在需要设计一个哈希值,以及运算方案,使得如果一个区间合法,这个区间的哈希值运算起来为某一个特定的值。

对于这种类括号匹配的的问题,只需要哈希值在这种运算下拥有结合律,而且不拥有交换律即可。确切来说,需要一个 非阿贝尔群

最简单的方案是选择二阶可逆矩阵构成的群。具体来说,随机产生三个可逆二阶方阵 \(A_1, A_2, A_3\),以及它们的逆 \(A_1^{-1}, A_2^{-1}, A_3^{-1}\)

\(i\) 个数的哈希值定义为 \(A_i\) 如果 \(i\) 是奇数,定义为 \(A_i^{-1}\) 如果它是偶数。(显然每次消除的两个相同的数的下标一定不同奇偶性)

如果区间所有哈希值的乘积为单位矩阵,则这个区间合法。

PS:我一开始尝试了使用对称群,但这样发生哈希冲突的概率不低,导致死活过不去。

总结

非阿贝尔群哈希用于解决类似括号匹配的问题很适合(它还能推广到 \(k\) 个不同类型括号的情况),算一个经典的 trick。

本题这种显然要数据结构,并且要求输出 YesNo 的题,可以尝试数据结构维护 hash,因为 hash 适合解决这类判断性的问题。

Code

#include <bits/stdc++.h>
#include <iostream>
#include <random>
using namespace std;
typedef long long ll;
#define rep(i, a, b) for(ll i=a;i<=b;i++)
#define rrep(i, a, b) for(ll i=a;i>=b;i--)
const ll N=5e5+9;//选择14阶对称群
ll MOD=998244353;
//!建议在程序下方的注释中记录为了调试,做了哪些临时的改动,需要在提交前改回来的
ll n, q;
ll a[N];
string s;
mt19937 rnd(MOD);
ll power(ll u, ll t){
    ll res=1;
    while(t){
        if(t&1)(res*=u)%=MOD;
        (u*=u)%=MOD;
        t>>=1;
    }
    return res;
}
struct replacement{//(其实是二阶矩阵,我一开始用的是对称群,懒得改名了)
    ll a[2][2];
    replacement operator*(const replacement& b)const{
        replacement c;c.clear();
        rep(i, 0, 1){
            rep(j, 0, 1){
                c.a[i][j]=(a[i][0]*b.a[0][j]+a[i][1]*b.a[1][j])%MOD;
            }
        }
        return c;

    }
    replacement inverse()const{
        replacement c;c.clear();
        ll det=a[0][0]*a[1][1]-a[0][1]*a[1][0];det%=MOD;det+=MOD;det%=MOD;
        det=power(det, MOD-2);
        c.a[0][0]=a[1][1]*det%MOD;
        c.a[0][1]=-a[0][1]*det%MOD;c.a[0][1]=(c.a[0][1]+MOD)%MOD;
        c.a[1][0]=-a[1][0]*det%MOD;c.a[1][0]=(c.a[1][0]+MOD)%MOD;
        c.a[1][1]=a[0][0]*det%MOD;
        return c;
    }
    void initial(){
        a[0][0]=a[1][1]=1;a[0][1]=a[1][0]=0;//!没有初始化完全
    }
    void print(){
        rep(i, 0, 1){
            rep(j, 0, 1){
                cout << a[i][j] << ' ';
            }
            cout << endl;
        }
    }
    void clear(){
        a[0][0]=a[0][1]=a[1][0]=a[1][1]=0;
    }
    bool isBasic(){
        if(a[0][0]==1&&a[1][1]==1&&a[0][1]==0&&a[1][0]==0)return 1;
        return 0;
    }
    void randomSet(){
        a[0][0]=rnd()%MOD;
        a[0][1]=rnd()%MOD;
        a[1][0]=rnd()%MOD;
        a[1][1]=rnd()%MOD;
    }
};
struct point{
    ll l, r;
    replacement f[3];
    ll tag;//!啊啊啊我怎么忘记区间操作要懒标记了
    void change(){
        replacement tmp=f[0];
        f[0]=f[1];f[1]=f[2];f[2]=tmp;
    }
}tr[N*4];

replacement st[3];
void clear(){
    rep(u, 0, 4*n){
        tr[u].l=tr[u].r=tr[u].tag=0;
        rep(j, 0, 2)tr[u].f[j].clear();
    }
    st[0].clear();st[1].clear();st[2].clear();s.clear();n=0;q=0;
}
void buildtree(ll u, ll l, ll r){
    tr[u].l=l;tr[u].r=r;tr[u].tag=0;
    if(l==r){
        tr[u].f[0]=st[s[l]-'0'];
        tr[u].f[1]=st[(s[l]-'0'+1)%3];
        tr[u].f[2]=st[(s[l]-'0'+2)%3];
        if(l&1){tr[u].f[0]=tr[u].f[0].inverse();tr[u].f[1]=tr[u].f[1].inverse();tr[u].f[2]=tr[u].f[2].inverse();}
        return;
    }
    ll mid=(l+r)>>1;
    buildtree(u<<1, l, mid);
    buildtree(u<<1|1, mid+1, r);
    tr[u].f[0]=tr[u<<1].f[0]*tr[u<<1|1].f[0];
    tr[u].f[1]=tr[u<<1].f[1]*tr[u<<1|1].f[1];
    tr[u].f[2]=tr[u<<1].f[2]*tr[u<<1|1].f[2];
}
void push_down(ll u){
    rep(i, 1, tr[u].tag){
        (tr[u*2].tag+=1)%=3;tr[u*2].change();
        (tr[u*2+1].tag+=1)%=3;tr[u*2+1].change();
    }
    tr[u].tag=0;
}
void update(ll u, ll l, ll r){
    if(tr[u].l>=l&&tr[u].r<=r){
        (tr[u].tag+=1)%=3;
        tr[u].change();
        return;
    }
    push_down(u);
    ll mid=(tr[u].l+tr[u].r)>>1;
    if(l<=mid)update(u<<1, l, r);
    if(r>mid)update(u<<1|1, l, r);
    tr[u].f[0]=tr[u<<1].f[0]*tr[u<<1|1].f[0];
    tr[u].f[1]=tr[u<<1].f[1]*tr[u<<1|1].f[1];
    tr[u].f[2]=tr[u<<1].f[2]*tr[u<<1|1].f[2];
}
replacement query(ll u, ll l, ll r){
    if(tr[u].l>=l&&tr[u].r<=r){return tr[u].f[0];}
    push_down(u);//!query也需要push_down,忘记了
    ll mid=(tr[u].l+tr[u].r)>>1;
    replacement res;res.initial();
    if(l<=mid)res=res*query(u<<1, l, r);
    if(r>mid)res=res*query(u<<1|1, l, r);
    return res;
}
ll T;
int main(){

    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

    cin >> n >> q;
    cin >> s;
    s="#"+s;
    rep(i, 0, 2){
        st[i].randomSet();
    }
    buildtree(1, 1, n);
    rep(t, 1, q){
        ll op, l, r;cin >> op >> l >> r;
        if(op==2){
            replacement res=query(1, l, r);
            bool ok=res.isBasic();
            if(ok)cout << "YES\n";
            else cout << "NO\n";
        }else{
            update(1, l, r);
        }
    }

    return 0;
}

posted @ 2025-06-25 14:31  yanzihe  阅读(16)  评论(0)    收藏  举报