2023牛客暑期多校训练营2 GH「字符串哈希判回文」「前缀和+思维」

G

题意:

给你一个字符串,判断他是否由若干个对称串拼接而成,对称串的定义为:

  • 空串
  • 为o|s|x|z任一字母
  • 对称串的两端分别加对称字母,即S=bSq∣dSp∣pSd∣qSb∣nSu∣uSn|oSo∣sSs∣xSx∣zSz
思路:

其实就是字符串哈希判回文,这个东西的原理非常显然:如果某一段是回文,那么他正反哈希值是一样的

对于本题,判对称,我们可以搞一个与它对称的字符串,对称字符串倒序和原字符串是段段相等的,然后哈希就可以了。

jls的做法为把对称字符串倒序加到原字符串后,直接一段段判相等即可,例如:

s b z z q − > s b z z q b z z q s sbzzq -> sbzzqbzzqs sbzzq>sbzzqbzzqs

在转变后的字符串中,每段对称串都是前后相等的,当该段不为对称段时,是不相等的,例如:

b x s x x q − > b x s x x q b x x s x q bxsxxq->bxsxxqbxxsxq bxsxxq>bxsxxqbxxsxq

这个太好写了但是我看不懂jls的哈希板子,所以用一个常见一点的哈希板子。

(ghs1是正着哈希,ghs2是反着哈希,所以这个板子的t字符串不用翻转。

AC代码
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 1e6 + 50;
const int B=1331;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
string a="bdpqnuosxz";
string b="qpdbunosxz";
map<char,char>mp;

struct Hash {
    ll p, MOD, pp[N];
    ll pre[N], suf[N];
    Hash(int P, int mod) {
        p = P; MOD = mod;
    }
    void init(char *s, char *t, int n) {
        pp[0] = 1; pre[0]=0;suf[n + 1] = 0;
        for (int i = 1; i <= n; i++) {
            pp[i] = pp[i - 1] * p % MOD;
            pre[i] = (pre[i - 1] * p + s[i]) % MOD;
        }
        for (int i = n; i >= 1; i--) {
            suf[i] = (suf[i + 1] * p + t[i]) % MOD;
        }
    }
    ll ghs1(int l, int r) {
        return (pre[r] - pre[l - 1] * pp[r - l + 1] % MOD + MOD) % MOD;
    }
    ll ghs2(int l, int r) {
        return (suf[l] - suf[r + 1] * pp[r - l + 1] % MOD + MOD) % MOD;
    }
    bool equal(int l1, int r1, int l2, int r2) {
        return ghs1(l1, r1) == ghs2(l2, r2);
    }
}hs1(233, 998244353), hs2(1331, 1000000007);

char s[N],t[N];
void work() {
    scanf("%s",s+1);
    int n=strlen(s+1);
    
    for(int i=1;i<=n;++i){
        if(a.find(s[i])==-1){
            cout<<"No\n";return;
        }
    }
    for(int i=1;i<=n;++i){
        t[i]=b[a.find(s[i])];
    }
    hs1.init(s,t,n);
    hs2.init(s,t,n);
    int l=1;
    for(int i=1;i<=n;++i){
        if(hs1.equal(l,i,l,i)&&hs2.equal(l,i,l,i))l=i+1;
    }
    if(l==n+1)cout<<"Yes\n";
    else cout<<"No\n";
}

signed main() {
    //io;
    int t;
    cin >> t;
    while (t--) {
        work();
    }
    return 0;
}

H

题意:

对于一个01字串x有以下两个操作:

  • A:将x中的0和1翻转
  • B:执行x=x+1,特别的当x全为1时,加1变全0

现在给定一个AB串,有以下q个询问,每个询问给出l,r,x,求出经过AB串的l->r操作后,x会变成什么。

强制在线做法,每次询问的真实l,r由上次的答案决定。

思路:

前缀和

首先注意到先做B再做A相当于先做A再减1,对于每次A翻转有 ( x + x ′ + 1 ) = 0 (x+x'+1)=0 (x+x+1)=0

考虑若干个操作后AB对于结果的影响:
偶数个A操作代表不翻转奇数个A操作代表翻转;在奇数个A前的B对结果的影响是-1,在偶数个A前的B对结果的影响是+1

做出A,前面有偶数个A的B,前面有奇数个A的B的前缀和。
对于某一段,前缀和直接得出该段中A的奇偶性判断是否需要翻转。设o=Bi->r中A的个数的奇偶性,x=1->Bi中A的个数的奇偶性,g=1->r中A的个数的奇偶性,易得 o = x ⊕ g o=x\oplus g o=xg
g是确定的,我们已经前缀出了符合x特性的B的个数,假定g为奇数,则add为本段里使x为1的B个数-使x为0的B个数,如果g是偶数,则把add取负即可。

注意到add有可能是极小的数,所以需要先取模。(呜呜呜被这卡了一晚上)

AC代码
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
int a[N];
int b[N];//b_0
int c[N];//b_1

void work() {
    int n,q;cin>>n>>q;
    string s;cin>>s;
    for(int i=1;i<=n;++i){
        if(s[i-1]=='A')a[i]=1;
        else b[i]=1;
    }
    for(int i=2;i<=n;++i){
        a[i]+=a[i-1];
        if((a[i]&1)&&b[i]==1){
            c[i]=1;
        }
        b[i]+=b[i-1];
    }
    for(int i=1;i<=n;++i){
        c[i]+=c[i-1];
        b[i]-=c[i];
    }
    ll lst=0;
    while(q--){
        ll l,r;
        cin>>l>>r;
        l=(l^lst)%n;r=(r^lst)%n;
        if(l>r)swap(l,r);
        l++;r++;

        string ss;
        cin>>ss;
        int sl=ss.length();
        ll x=0,m=1ll<<sl;
        for(int i=0;i<sl;++i){
            x*=2;x+=(ss[i]-'0');
        }
        //翻转
        if((a[r]-a[l-1])&1){
          x=m-1-x;
        }
        //o=x^g
        ll add=b[r]-c[r]-b[l-1]+c[l-1];//偶-奇,偶加奇减
        if(a[r]&1){//g为a[r]的奇偶性
            add*=(-1);
        };
        add%=m;
        x=(x+add+m)%m;
        lst=x;
        for(int i=sl-1;i>=0;--i){
            if(x>>i&1)cout<<"1";
            else cout<<"0";
        }
        cout<<'\n';
    }
}

signed main() {
    io;
    int t=1;
    //cin >> t;
    while (t--) {
        work();
    }
    return 0;
}

E签到的枚举
F队友都不知道怎么莫名其妙就过了的博弈
I五子棋!
K队友指导我写的dp
队友带飞我呜呜呜浅补两个好了()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值