【Code Forces 321E】【线段树+字符串哈希】Kefa and Watch 字符串修改 循环节判定

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<ctype.h>
#include<math.h>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<functional>
#include<string>
#include<algorithm>
#include<time.h>
#include<bitset>
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T> inline void gmax(T &a,T b){if(b>a)a=b;}
template <class T> inline void gmin(T &a,T b){if(b<a)a=b;}
using namespace std;
const int N=1e5+10,M=0,Z=1e9+7,maxint=2147483647,ms31=522133279,ms63=1061109567,ms127=2139062143;const double eps=1e-8,PI=acos(-1.0);//.0
int n,m,g;
const int W=13;
LL b[N],s[N];
struct A
{
    int l,r;
    int v,w;
}a[1<<18];
void pushup(int o)
{
    a[o].v=(a[ls].v+a[rs].v)%Z;
}
void pushdown(int o)
{
    if(~a[o].w)
    {
        a[ls].w=a[rs].w=a[o].w;
        a[ls].v=(s[a[ls].r]-s[a[ls].l-1]+Z)*a[o].w%Z;
        a[rs].v=(s[a[rs].r]-s[a[rs].l-1]+Z)*a[o].w%Z;
        a[o].w=-1;
    }
}
void build(int o,int l,int r)
{
    a[o].l=l;
    a[o].r=r;
    a[o].w=-1;
    if(l==r)
    {
        char ch;scanf("%c",&ch);
        int x=ch-48;
        a[o].v=x*b[l]%Z;
        return;
    }
    int m=(l+r)>>1;
    build(ls,l,m);
    build(rs,m+1,r);
    pushup(o);
}
void change(int o,int l,int r,int w)
{
    if(a[o].l==l&&a[o].r==r)
    {
        a[o].w=w;
        a[o].v=(s[r]-s[l-1]+Z)*w%Z;
        return;
    }
    pushdown(o);
    int m=(a[o].l+a[o].r)>>1;
    if(r<=m)change(ls,l,r,w);
    else if(l>m)change(rs,l,r,w);
    else
    {
        change(ls,l,m,w);
        change(rs,m+1,r,w);
    }
    pushup(o);
}
int check(int o,int l,int r)
{
    if(a[o].l==l&&a[o].r==r)
    {
        return a[o].v;
    }
    pushdown(o);
    int m=(a[o].l+a[o].r)>>1;
    if(r<=m)return check(ls,l,r);
    else if(l>m)return check(rs,l,r);
    else return (check(ls,l,m)+check(rs,m+1,r))%Z;
}
int main()
{
    //预处理,b[i]表示第i个位置对应的哈希映射值;s[i]表示前i个位置对应的哈希映射值之和
    b[0]=0;b[1]=1;for(int i=2;i<N;i++)b[i]=b[i-1]*W%Z;
    s[0]=0;s[1]=1;for(int i=2;i<N;i++)s[i]=(s[i-1]+b[i])%Z;
    while(~scanf("%d%d%d",&n,&m,&g))
    {
        getchar();
        build(1,1,n);
        m+=g;
        while(m--)
        {
            int o,l,r,w;
            scanf("%d%d%d%d",&o,&l,&r,&w);
            if(o==1)change(1,l,r,w);
            else
            {
                if(r-l<w)printf("YES\n");
                else//r-w>=l
                {
                    int v1=check(1,l,r-w);v1=v1*b[w+1]%Z;
                    int v2=check(1,l+w,r);
                    if(v1==v2)printf("YES\n");
                    else printf("NO\n");
                }
            }
        }
    }
    return 0;
}
/*
【题意】
给你一个长度为n(1e5)的数字串。
给你m(1e5)个操作

操作包括——
1,把范围l~r的数都变成w
2,问你,范围l~r的数是否拥有长度为w的循环节

【类型】
线段树
字符串哈希

【分析】
首先,数据规模提示了我们要使用数据结构。
然后,这里面临的最大问题就是——如何快速判定一个区间范围内的数是否有长度为w的循环节
而且单次判定的时间要在log级别。

我们观察到,题目给出的是数串,也是字符串,只有'0'-'9'。
为什么不是1e9范围的数呢?
数串(字符串)可以怎样呢?可以统计,也可以——哈希!

为什么我们想到字符串哈希呢?
长度为l,循环节为w,段数就是(l+w-1)/l,我们如果逐一段数比较,是可以完成判定的。
然而这种方法时间复杂度很高,尤其当w很小的时候,就爆炸啦!

结合于类似KMP的性质,我们发现,字符串[l,r]有长度为w的循环节,只需要使得[l,r-w]=[l+w,r]就可以了!

于是,修改操作我们可以用线段树完成。
线段树维护的是字符串哈希的区间值。
这样就可以在O(nlogn)的时间内解决这道题。

哇咔咔,竟然做了第16名,哈哈~

=========================================================
字符串哈希是怎样实现呢?
从前到后,位置i从1到n,每个位点对应的BAS分别是bas^(i-1).
也就是说我映射的结果下,第一位反而是最低位。

这个不同于传统哈希的前缀和统计。
因为这题设计到动态修改,所以才采用逐位求BAS的方法。

这样,在我们确定某个区间段的哈希值的时候,直接利用线段树的区间统计即可。
如果存在修改操作。
我们只需要找到该区间段,利用前缀和求出区间BAS和,然后*修改后的数字计数即可。
因为涉及到区间修改,所以需要使用延迟标记。

这题很自然很简单地就AC了。

【时间复杂度&&优化】
O(nlogn)
线段树一定要养成写pushup和pushdown的好习惯
在某个区间段打下延迟标记的时候立刻修改该区间段的值也是个不容易出错的写法
要学会观察数据范围,字符串哈希算法是个不错的选择。

【trick】

【数据】
Input
6
2 2 1 3 4 1
Output
3

Input
3
2 2 9
Output
3

more——
20 1 3
11111111111111111111
2 1 20 19
1 1 1 2
2 1 20 19
2 2 20 10
ANS=YES NO YES
*/
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值