LOJ#2444. 「NOI2011」阿狸的打字机

本文介绍了一种基于打字机输入的字符串匹配算法,通过构建Trie树和使用树状数组维护子树和,实现了高效的字符串匹配和查询功能。文章详细解释了算法原理,并提供了两种实现方式的代码示例。

题目描述

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有 \(28\) 个按键,分别印有 \(26\) 个小写英文字母和 BP 两个字母。 经阿狸研究发现,这个打字机是这样工作的:

  • 输入小写字母,打字机的一个凹槽中会加入这个字母(按 P 前凹槽中至少有一个字母)。
  • 按一下印有 B 的按键,打字机凹槽中最后一个字母会消失。
  • 按一下印有 P 的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失(保证凹槽中至少有一个字母)。

例如,阿狸输入 aPaPBbP ,纸上被打印的字符如下:

a 
aa 
ab

我们把纸上打印出来的字符串从 \(1\) 开始顺序编号,一直到 \(n\) 。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数 \((x,y)\) (其中 \(1 \le x,y \le n\) ),打字机会显示第 \(x\) 个打印的字符串在第 \(y\) 个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

输入格式

输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数 \(m\) ,表示询问个数。 接下来 \(m\) 行描述所有由小键盘输入的询问。其中第i行包含两个整数 \(x, y\) ,表示第i个询问为 \((x, y)\)

输出格式

输出 \(m\) 行,其中第 \(i\) 行包含一个整数,表示第 \(i\) 个询问的答案。

样例

样例输入
aPaPBbP 
3 
1 2
1 3 
2 3
样例输出
2
1
0

数据范围与提示

所有测试数据的范围和特点如下表所示:

测试点编号\(n\) 的规模\(m\) 的规模字符串长度输入总长 (输入文件第一行的字符数)
1$ 1\le n \le 100$$ 1\le m \le 1000$-\(\le 100\)
2$ 1\le n \le 100$$ 1\le m \le 1000$-\(\le 100\)
3$ 1\le n \le 1000$$ 1\le m \le 10^4$单个长度 $ \le 1000$ ,总长度 $ \le 10^5$\(\le 10^5\)
4$ 1\le n \le 1000$$ 1\le m \le 10^4$单个长度 $ \le 1000$ ,总长度 $ \le 10^5$\(\le 10^5\)
5$ 1\le n \le 10^4$$ 1\le m \le 10^5$总长度 $ \le 10^5$\(\le 10^5\)
6$ 1\le n \le 10^4$$ 1\le m \le 10^5$总长度 $ \le 10^5$\(\le 10^5\)
7$ 1\le n \le 10^4$$ 1\le m \le 10^5$总长度 $ \le 10^5$\(\le 10^5\)
8$ 1\le n \le 10^5$$ 1\le m \le 10^5$-\(\le 10^5\)
9$ 1\le n \le 10^5 $$ 1\le m \le 10^5$-\(\le 10^5\)
10$ 1\le n \le 10^5$$ 1\le m \le 10^5$-\(\le 10^5\)

题解

一个比较直观的想法就是按照y来排序,然后对于所有y相同的x询问都只跑一次y,然后用个桶记录答案就可以了。这样能拿70分。

换一个方向来写,将Trie树上属于y的那条链搞上+1的标记,就转化成子树和的问题了,这个可以搞下来dfs序后用树状数组维护子树和。

但是注意处理dfs序的时候,连的边必须是该点的fail向该点连的边(这样才能匹配)。

那么在trie树上dfs一遍(不能是Trie图会进环),每次dfs到一个y的结尾,那么就把对应的x处理了(即查询x子树的区间)。

并且插入字符串要注意边读入边处理,不然读入完后每次重新处理一个串(如70分代码所示)光插入到Trie里面就TLE了...

70分代码

#include <bits/stdc++.h>
using namespace std;

#define N 100010
int ch[N][26], fail[N], last[N], q[N], val[N];
int cnt = 0, tot = 0, num = 0;
int vis[N], ans[N];
char s[N];
vector<char>st[N];
struct Node {
    int x, y, id;
}Q[N];

void insert() {
    int u = 0; ++num;
    for(int i = 1; i <= cnt; ++i) {
        int c = s[i] - 'a';
        st[num].push_back(s[i]);
        if(!ch[u][c]) ch[u][c] = ++tot;
        u = ch[u][c];
    }
    val[u] = num;
}

bool cmp(Node a, Node b) {
    if(a.y == b.y) return a.x < b.x;
    return a.y < b.y;
}

void get_fail() {
    int l = 1, r = 1;
    for(int i = 0; i < 26; ++i) if(ch[0][i]) q[r++] = ch[0][i];
    while(l != r) {
        int u = q[l++];
        if(l == 100000) l = 1;
        for(int i = 0; i < 26; ++i) {
            if(ch[u][i]) {
                fail[ch[u][i]] = ch[fail[u]][i];
                q[r++] = ch[u][i];
                if(r == 100000) r = 1;
            } else ch[u][i] = ch[fail[u]][i];
            last[ch[u][i]] = val[fail[ch[u][i]]] ? fail[ch[u][i]] : last[fail[ch[u][i]]];
        }
    }
}

void query(int id) {
    for(int i = 1; i <= num; ++i) vis[i] = 0;
    int u = 0, len = st[id].size();
    for(int i = 0; i < len; ++i) {
        u = ch[u][st[id][i] - 'a'];
        for(int j = u; j; j = last[j]) {
            if(val[j]) vis[val[j]]++;
        }
    }
}

int main() {
    char c = getchar();
    while(c != '\n') {
        if(c == 'B') --cnt;
        else if(c == 'P') insert();
        else s[++cnt] = c;
        c = getchar();
    }
    int m;
    scanf("%d", &m);
    for(int i = 1, x, y; i <= m; ++i) {
        scanf("%d%d", &x, &y);
        Q[i] = (Node) {x, y, i};
    } 
    sort(Q + 1, Q + m + 1, cmp);
    get_fail();
    for(int i = 1; i <= m; ++i) {
        if(Q[i].y != Q[i - 1].y) {
            query(Q[i].y);
            ans[Q[i].id] = vis[Q[i].x];
        } else ans[Q[i].id] = vis[Q[i].x];
    }
    for(int i = 1; i <= m; ++i) {
        printf("%d\n", ans[i]);
    }
    return 0;
}

100分做法

#include <bits/stdc++.h>
using namespace std;

#define N 100010
int ch[N][26], fail[N], lst[N], val[N];
int cnt = 0, tot = 0, n = 0;
int ql[N], qr[N], ans[N];
char s[N];
struct Node {
    int x, y, id;
}Q[N];
struct edge {
    int to, nxt;
}e[N];
int head[N];

inline void ins(int u, int v) {
    e[++cnt] = (edge) {v, head[u]};
    head[u] = cnt;
}

bool cmp(Node a, Node b) {
    if(a.y == b.y) return a.x < b.x;
    return a.y < b.y;
}

int q[N];
inline void get_fail() {
    int l = 1, r = 1;
    for(int i = 0; i < 26; ++i) if(ch[0][i]) q[r++] = ch[0][i];
    while(l != r) {
        int u = q[l++];
        if(l == 100000) l = 1;
        for(int i = 0; i < 26; ++i) {
            if(ch[u][i]) {
                fail[ch[u][i]] = ch[fail[u]][i];
                q[r++] = ch[u][i];
                if(r == 100000) r = 1;
            } else ch[u][i] = ch[fail[u]][i];
        }
    }
}

int tim, dfn[N], low[N];
int t[N][26], fa[N];
void pre_dfs(int u) {
    dfn[u] = ++tim;
    for(int i = head[u]; i; i = e[i].nxt) pre_dfs(e[i].to);
    low[u] = tim;
}

int c[N];
inline int lowbit(int x) { return x & -x; }
inline void add(int x, int val) {
    for(int i = x; i <= tim; i += lowbit(i)) c[i] += val;
}
inline int query(int x) {
    int ans = 0;
    for(int i = x; i; i -= lowbit(i)) ans += c[i];
    return ans;
}

void dfs(int u) {
    add(dfn[u], 1);
    if(val[u])
        for(int i = ql[val[u]]; i <= qr[val[u]]; ++i)  
            ans[Q[i].id] = query(low[lst[Q[i].x]]) - query(dfn[lst[Q[i].x]] - 1);
    for(int i = 0; i < 26; ++i) if(t[u][i]) dfs(t[u][i]); 
    add(dfn[u], -1);
}

int main() {
    int u = 0;
    char c = getchar();
    while(c != '\n') {
        if(c >= 'a' && c <= 'z') {
            if(!ch[u][c - 'a']) ch[u][c - 'a'] = ++tot, fa[tot] = u;;
            u = ch[u][c - 'a'];
        } 
        if(c == 'B') u = fa[u];
        if(c == 'P') lst[++n] = u, val[u] = n;
        c = getchar();
    }
    int m;
    scanf("%d", &m);
    for(int i = 1, x, y; i <= m; ++i) {
        scanf("%d%d", &x, &y);
        Q[i] = (Node) {x, y, i};
    }
    sort(Q + 1, Q + m + 1, cmp);
    for(int i = 0; i <= tot; ++i) 
        for(int j = 0; j < 26; ++j) t[i][j] = ch[i][j];
    get_fail(); cnt = 0;
    for(int i = 1; i <= tot; ++i) ins(fail[i], i);
    pre_dfs(0); 
    for(int i = 1, p = 1; i <= m; i = p) {
        ql[Q[i].y] = i;
        while(Q[p].y == Q[i].y) ++p;
        qr[Q[i].y] = p - 1;
    }
    dfs(0);
    for(int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
    return 0;
}

转载于:https://www.cnblogs.com/henry-1202/p/10319817.html

06-21
<think>我们正在查询LOJ6279相关的编程题目或解决方案。LOJ(LibreOJ)是一个在线的评测系统,主要收录算法竞赛题目。根据题号6279,我们需要确定该题目的具体内容。由于我无法直接访问网络,我将基于已知信息进行推理。在算法竞赛中,LOJ的题目编号通常特定的题目对应。题号6279可能对应一个特定的问题。根据常见的题目类型,我们可以推测它可能涉及数据结构算法。然而,为了准确回答,我们可以尝试回忆或推断常见的LOJ题目。例如,LOJ6000系列题目多为数据结构题,如线段树、树状数组等。实际上,通过搜索记忆,我回想起LOJ6279是“数列分块入门3”(BlockArray3)题目。这是“分块”系列题目中的一题。题目描述:给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(即最大的小于x的数)。因此,用户需要的是关于LOJ6279题目的描述和解决方案。解决方案:由于题目要求分块处理,我们将数列分成若干块,每块大小为sqrt(n)。对于每个块,我们维护一个有序的数组(用于二分查找)以及一个加法标记(记录整个块被加了多少)。操作:1.区间加法:对于整块,直接更新加法标记;对于不完整的块,暴力更新元素值,然后重新排序该块(或者重新构建有序数组)。2.查询区间内小于x的前驱:遍历区间内的所有块。对于整块,在块的有序数组中二分查找小于(x-加法标记)的最大值;对于不完整的块,暴力遍历每个元素,判断是否小于x(注意加上加法标记)并更新最大值。代码实现(C++)示例:```cpp#include<iostream>#include<vector>#include<cmath>#include<algorithm>#include<climits>usingnamespacestd;constintMAXN=100010;constintBLOCK_SIZE=320;//约sqrt(MAXN)intn;vector<int>a;vector<int>block_add;//每块的加法标记vector<vector<int>>sorted_blocks;//每块的有序数组voidinit(){intblock_num=(n+BLOCK_SIZE-1)/BLOCK_SIZE;block_add.assign(block_num,0);sorted_blocks.resize(block_num);for(inti=0;i<n;i++){sorted_blocks[i/BLOCK_SIZE].push_back(a[i]);}for(inti=0;i<block_num;i++){sort(sorted_blocks[i].begin(),sorted_blocks[i].end());}}voidupdate_block(intblock_id){sorted_blocks[block_id].clear();intstart=block_id*BLOCK_SIZE;intend=min((block_id+1)*BLOCK_SIZE,n);for(inti=start;i<end;i++){sorted_blocks[block_id].push_back(a[i]);}sort(sorted_blocks[block_id].begin(),sorted_blocks[block_id].end());}voidadd(intl,intr,intc){intblock_l=l/BLOCK_SIZE,block_r=r/BLOCK_SIZE;if(block_l==block_r){for(inti=l;i<=r;i++){a[i]+=c;}update_block(block_l);}else{for(inti=l;i<(block_l+1)*BLOCK_SIZE;i++){a[i]+=c;}update_block(block_l);for(inti=block_l+1;i<block_r;i++){block_add[i]+=c;}for(inti=block_r*BLOCK_SIZE;i<=r;i++){a[i]+=c;}update_block(block_r);}}intquery(intl,intr,intc){intblock_l=l/BLOCK_SIZE,block_r=r/BLOCK_SIZE;intans=-1;if(block_l==block_r){for(inti=l;i<=r;i++){intval=a[i]+block_add[block_l];if(val<c){if(val>ans)ans=val;}}}else{for(inti=l;i<(block_l+1)*BLOCK_SIZE;i++){intval=a[i]+block_add[block_l];if(val<c){if(val>ans)ans=val;}}for(inti=block_l+1;i<block_r;i++){//在块i的有序数组中二分查找小于c-block_add[i]的最大值inttarget=c-block_add[i];autoit=lower_bound(sorted_blocks[i].begin(),sorted_blocks[i].end(),target);if(it!=sorted_blocks[i].begin()){it--;intval=*it+block_add[i];if(val>ans)ans=val;}}for(inti=block_r*BLOCK_SIZE;i<=r;i++){intval=a[i]+block_add[block_r];if(val<c){if(val>ans)ans=val;}}}returnans;}intmain(){cin>>n;a.resize(n);for(inti=0;i<n;i++){cin>>a[i];}init();for(inti=0;i<n;i++){intop,l,r,c;cin>>op>>l>>r>>c;l--;r--;//如果题目输入是1-indexed,则转换为0-indexedif(op==0){add(l,r,c);}else{cout<<query(l,r,c)<<endl;}}return0;}```注意:上述代码是示例代码,可能需要根据题目具体输入要求进行调整。§§相关问题§§1.LOJ6279题目的具体描述是什么?2.分块算法的时间复杂度是多少?3.除了分块,还有哪些方法可以解决LOJ6279?4.如何优化分块算法的常数?5.分块算法适用于哪些场景?</think>### LOJ 6279 题目分析解决方案 **题目描述**:LOJ 6279 是 LibreOJ 上的 **"数列分块入门 3"**,属于分块算法的经典练习题。核心要求如下: - **操作 1**:区间 $[l, r]$ 内每个数加上值 $c$ - **操作 2**:查询区间 $[l, r]$ 内小于 $c$ 的**最大元素值**(即前驱),不存在时返回 $-1$ #### 分块算法设计 分块的核心思想是将数列划分为 $\sqrt{n}$ 大小的块,平衡暴力操作批量处理。 ##### 关键数据结构 1. **块内有序数组**: - 每块维护一个排序后的数组,用于二分查找前驱。 - 示例:块 $B_i$ 的排序数组为 $S_i$。 2. **加法懒标记**: - 记录整块被加的总值 $add_i$(不影响块内有序性)。 ##### 操作实现 1. **区间加法($[l, r] + c$)**: - **整块**:直接更新懒标记 $add_i \gets add_i + c$,时间复杂度 $O(1)$。 - **碎块**:暴力修改元素值,并**重构有序数组**(排序),时间复杂度 $O(\sqrt{n} \log \sqrt{n})$。 2. **查询前驱($[l, r]$ 内 $< c$ 的最大值)**: - **整块**:在有序数组 $S_i$ 中二分查找 $c - add_i$ 的前驱,时间复杂度 $O(\log \sqrt{n})$。 - **碎块**:暴力遍历元素,检查 $a_j + add_i < c$ 并更新最大值,时间复杂度 $O(\sqrt{n})$。 #### 代码实现(C++) ```cpp #include <iostream> #include <vector> #include <cmath> #include <algorithm> #include <climits> using namespace std; const int MAXN = 100010; int n, block_size; vector<int> a; vector<int> add_tag; // 块的加法标记 vector<vector<int>> blocks; // 每块的有序数组 void init() { block_size = sqrt(n); int block_num = (n + block_size - 1) / block_size; add_tag.assign(block_num, 0); blocks.resize(block_num); for (int i = 0; i < n; i++) blocks[i / block_size].push_back(a[i]); for (int i = 0; i < block_num; i++) sort(blocks[i].begin(), blocks[i].end()); } void rebuild_block(int block_id) { blocks[block_id].clear(); int start = block_id * block_size; int end = min(start + block_size, n); for (int i = start; i < end; i++) blocks[block_id].push_back(a[i]); sort(blocks[block_id].begin(), blocks[block_id].end()); } void range_add(int l, int r, int c) { int block_l = l / block_size, block_r = r / block_size; if (block_l == block_r) { // 同一块内 for (int i = l; i <= r; i++) a[i] += c; rebuild_block(block_l); } else { // 左侧碎块 for (int i = l; i < (block_l + 1) * block_size; i++) a[i] += c; rebuild_block(block_l); // 中间整块 for (int i = block_l + 1; i < block_r; i++) add_tag[i] += c; // 右侧碎块 for (int i = block_r * block_size; i <= r; i++) a[i] += c; rebuild_block(block_r); } } int query_predecessor(int l, int r, int c) { int ans = -1; int block_l = l / block_size, block_r = r / block_size; // 左侧碎块暴力查询 for (int i = l; i < min(r + 1, (block_l + 1) * block_size); i++) { int val = a[i] + add_tag[block_l]; if (val < c && val > ans) ans = val; } // 中间整块二分查找 for (int i = block_l + 1; i < block_r; i++) { int target = c - add_tag[i]; auto it = lower_bound(blocks[i].begin(), blocks[i].end(), target); if (it != blocks[i].begin()) { it--; int val = *it + add_tag[i]; if (val < c && val > ans) ans = val; } } // 右侧碎块暴力查询 if (block_l != block_r) { for (int i = block_r * block_size; i <= r; i++) { int val = a[i] + add_tag[block_r]; if (val < c && val > ans) ans = val; } } return ans; } int main() { cin >> n; a.resize(n); for (int i = 0; i < n; i++) cin >> a[i]; init(); for (int i = 0; i < n; i++) { int op, l, r, c; cin >> op >> l >> r >> c; l--; r--; // 转为0-indexed if (op == 0) range_add(l, r, c); else cout << query_predecessor(l, r, c) << endl; } return 0; } ``` #### 算法分析 - **时间复杂度**: - 单次修改/查询:$O(\sqrt{n} \log \sqrt{n})$(碎块排序主导)。 - 总操作 $m$ 次:$O(m \sqrt{n} \log n)$。 - **空间复杂度**:$O(n)$。 #### 优化技巧 1. **减少排序次数**: - 碎块修改时只重构受影响块的有序数组。 2. **块大小调整**: - 实测调整块大小为 $n^{0.6}$ 可能更快(需测试)。 #### 应用场景 分块算法适用于**强制在线**的区间问题(如 LOJ 的数列分块系列题),在 $O(\sqrt{n})$ 复杂度下平衡修改查询[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值