Flower's Land 2做题笔记 && 非阿贝尔群哈希
神仙 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。
本题这种显然要数据结构,并且要求输出 Yes 和 No 的题,可以尝试数据结构维护 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;
}

浙公网安备 33010602011771号