HDU3973 线段树 + 字符哈希

  题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3973 , 线段树 + 字符哈希,好题。

  又学了一种新的哈希方法,hhhh~



解法:

  想法是用P进制的数来表示一个字符串,由于可能数太大,所以就将转换成是十进制后的数模long long的最大值,这样虽然也有可能冲突,但是概率会非常小。这里的P可以随意取一个素数(我取的是31)。

  先用上面提到的哈希方法将W集合中的字符串都转成十进制数存在数组中,然后进行排序;每一次询问时候,将询问区间的子串转成十进制后二分查找是否在W集合中即可。

   思路虽然简单,但是实现起来还是比较麻烦,尤其是有很多细节需要注意。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <vector>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 100000 + 20;
const int p = 31;
char str[2000020];
LL Hash[maxn << 2] , P[maxn];
LL W[10000 + 5];
void init()
{
    P[0] = 1;
    for(int i = 1 ; i <= maxn ; i++)
        P[i] = P[i - 1] * p;
}
LL calhash(char str[])
{
    LL sum = 0;
    for(int i = 0 ; str[i] != ‘\0‘ ; i++) {
        sum = sum * p + str[i] - ‘a‘ + 1;
    }
    return sum;
}
int binary_search(int l , int r , LL a[] , LL x)
{
    int m = (l + r) >> 1;
    while(l <= r) {
        if(a[m] == x)
            return m;
        if(a[m] > x)
            r = m - 1;
        if(a[m] < x)
            l = m + 1;
        m = (l + r) >> 1;
    }
    return -1;
}
void PushUp(int l , int r , int rt)
{
    int m = (l + r) >> 1;
    Hash[rt] = Hash[rt << 1] * P[r - m] + Hash[rt << 1 | 1];
}
void build(int l , int r , int rt)
{
    if(l == r) {
        Hash[rt] = str[r] - ‘a‘ + 1;
        return;
    }
    int m = (l + r) >> 1;
    build(lson);
    build(rson);
    PushUp(l , r , rt);
}
void update(int x , int l , int r , int rt)
{
    if(l == r) {
        Hash[rt] = str[x] - ‘a‘ + 1;
        return;
    }
    int m = (l + r) >> 1;
    if(x > m)
        update(x , rson);
    else
        update(x , lson);
    PushUp(l , r , rt);
}
LL query(int L , int R , int l , int r , int rt)
{
    if(L <= l && R >= r) {
        return Hash[rt];
    }
    int m = (l + r) >> 1;
    if(m < L)
        return query(L , R , rson);
    else if(m >= R)
        return query(L , R , lson);
    else
        return query(L , m , lson) * P[R - m] + query(m + 1 , R , rson);
}
int main()
{
    int n , m , T;
    char ch[10] , s[10];
    init();
    cin >> T;
    for(int k = 1 ; k <= T ; k++)
    {
        scanf("%d" , &n);
        for(int i = 1 ; i <= n ; i++) {
            scanf("%s" , str);
            W[i] = calhash(str);
        }
        sort(W + 1 , W + n + 1);
        scanf("%s" , str);
        int len = strlen(str);
        build(0 , len - 1 , 1);
        printf("Case #%d:\n" , k);
        scanf("%d" , &m);
        while(m--) {
            scanf("%s" , ch);
            if(ch[0] == ‘Q‘) {
                int L , R;
                scanf("%d %d" , &L , &R);
                LL tmp = query(L , R , 0 , len - 1 , 1);
                if(binary_search(1 , n , W , tmp) != -1)
                    puts("Yes");
                else
                    puts("No");
            } else {
                int x;
                scanf("%d" , &x);
                scanf("%s" , s);
                str[x] = s[0];
                update(x , 0 , len - 1 , 1);
            }
        }
    }
    return 0;
}
时间: 2024-10-10 00:44:24

HDU3973 线段树 + 字符哈希的相关文章

CF213E Two Permutations 线段树维护哈希值

当初竟然看成子串了$qwq$,不过老师的$ppt$也错了$qwq$ 由于子序列一定是的排列,所以考虑插入$1$到$m$到$n-m+1$到$n$; 如何判断呢?可以用哈希$qwq$: 我们用线段树维护哈希值,合并时用就把左子树的哈希值$x[ls]$在$B$进制下左移$sum[rs]$位,即$x[tr]=x[ls]*p[sum[rs]]+x[rs]$; 此时就可以向上更新哈希值. #include<cstdio> #include<iostream> #include<algor

HDU 4339 (线段树字符标记)

题意:两字符串s1,s2,给定若干查询. 查询操作: 1 a b c 把第a个字符串的第b个字符换成字符c 2 a   查询从第a个字符开始s1[k]==s2[k],的个数.例如 aaa aab 查询 2 0 结果是2 思路:线段树的叶子值设为出现不同的位置i+1,初始为len+1:没次查询区间最小值,即最先不同的位置: #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio&g

线段树+哈希【CF580E】Kefa and Watch

Description \(n\)个数的字符串,\(m + k\)个操作 1 l r k把\(l - r\)赋值为\(k\) 2 l r d询问\(l - r\)是否有长度为\(d\)的循环节 \(n \leq 10^5, m + k \leq 10^5, d \leq 10\) Input 第一行为三个整数\(n,m,k\) 第二行为一个\(n\)个数的字符串. 接下来\(m+k\)行每行对应一种操作. Output 对于每一个\(2\)操作,如果存在,输出一行\(YES\),否则输出\(NO

HLG 1808 绘画 (哈希 + map + 线段树)

链接: http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1808 Description 小胖子最近喜欢上了画画,但是他的画画技术很差,只能从零开始,小胖子在一条线上画了很多彩色的球,但是觉得不好看,就想修改. 小胖子站在画前想了又想,他有时会将一些连续的点涂成相同的颜色.但他感觉累的时候,就会无聊的数数某个颜色在某段区间内出现的次数. Input 每组数据的第一行输入两个正整数n和m(1&l

F - Problem F( 线段树 )

度熊手上有一本字典存储了大量的单词,有一次,他把所有单词组成了一个很长很长的字符串.现在麻烦来了,他忘记了原来的字符串都是什么,神奇的是他竟然记得原来那些字符串的哈希值.一个字符串的哈希值,由以下公式计算得到: H(s)=∏i≤len(s)i=1(Si?28) (mod 9973)SiSi代表 S[i] 字符的 ASCII 码. 请帮助度熊计算大字符串中任意一段的哈希值是多少. Input多组测试数据,每组测试数据第一行是一个正整数NN,代表询问的次数,第二行一个字符串,代表题目中的大字符串,接

hdu 5685 Problem A(2016&quot;百度之星&quot; - 资格赛(Astar Round1)——线段树)

题目链接:acm.hdu.edu.cn/showproblem.php?pid=5685 Problem A Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 564    Accepted Submission(s): 236 Problem Description 度熊手上有一本字典存储了大量的单词,有一次,他把所有单词组成了一个很长

线段树详解 (原理,实现与应用)

线段树详解 By 岩之痕 目录: 一:综述 二:原理 三:递归实现 四:非递归原理 五:非递归实现 六:线段树解题模型 七:扫描线 八:可持久化 (主席树) 九:练习题 一:综述 假设有编号从1到n的n个点,每个点都存了一些信息,用[L,R]表示下标从L到R的这些点. 线段树的用处就是,对编号连续的一些点进行修改或者统计操作,修改和统计的复杂度都是O(log2(n)). 线段树的原理,就是,将[1,n]分解成若干特定的子区间(数量不超过4*n),然后,将每个区间[L,R]都分解为 少量特定的子区

【XSY1551】往事 广义后缀数组 线段树合并

题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\)最长公共前缀,\(LCS=\)最长公共后缀 \(1\leq n\leq 200000\),字符集为\(\{0\ldots 300\}\) 题解 我们先看看这个\(LCP(s_u,s_v)\)怎么求 广义后缀自动机不行.广义后缀树可能可以,但我不会.广义后缀数组可以.然后我就开始手推广义后缀数组 广义

HDU 1754 I Hate It(线段树之单点更新,区间最值)

I Hate It Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 70863    Accepted Submission(s): 27424 Problem Description 很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少. 这让很多学生很反感.不管你喜不喜欢,现在需要你做的是,就是按照老师的