原题地址:http://acm.fzu.edu.cn/problem.php?pid=2280
题意:给你
个字符串,每个字符串有一个值
,有
次询问,一共两种操作:一是
表示把第
个串的
变为
;二是“
,输出第
个串能放几次魔法。放魔法的条件是这样:用串x放魔法,如果在1~n个串中,一个串的
不超过
的
并且
是这个串的后缀,则算放了一次魔法。
哈希写法
思路:用哈希值表示每一个字符串,然后用一个数组记录权值,一个数字记录字符串长度,每次询问就O(n)直接找就行了.
不得不吐槽一句这个OJ 真的毒瘤,一开始以为每次O(n)的遍历会T,然后set里面套了pair去二分查找,结果二分T了,然后发现直接遍历过了…….
下面注释掉的是本来二分的写法,希望有大佬能解释一下原因
//#include <bits/stdc++.h>
#include <cmath>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#include <stack>
#include <set>
#include <map>
#include <cctype>
#define eps 1e-8
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define lson l,mid,rt<<1
#define rson mid+1,r,(rt<<1)+1
#define CLR(x,y) memset((x),y,sizeof(x))
#define fuck(x) cerr << #x << "=" << x << endl
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int seed = 131;
const int maxn = 1e5 + 5;
const int mod = 998244353;
typedef pair<int, int>pii; //第1维是权值,第2维是第几个字符串
int a[maxn];//记录第i个字符串的权值,方便后面删除
int T, n, q;
multiset<pii>P;
multiset<pii>::iterator it, iter;
char str[1005][1005];
ull p[maxn], h[1005][1005];
int length[maxn];
void Hash(int id, char *s) {//处理第id个字符串的哈希值
h[id][0] = 0;
for (int i = 1; i <= length[id]; i++) h[id][i] = h[id][i - 1] * seed + s[i];
}
ull get_has(int id, int l, int r) {//获得第id个字符串[l,r]区间的哈希值
return h[id][r] - h[id][l - 1] * p[r - l + 1];
}
//int check(int x) {
// iter = P.upper_bound(make_pair(a[x], 1001));
// int ans = 0;
// int len = strlen(str[x] + 1);
// ull STD = get_has(x, 1, len);
// for ( it = P.begin(); it != iter; it++) {
// int mid = it->second;
// int lenmid = strlsen(str[mid] + 1);
// if (lenmid < len) continue;
// ull my = get_has(mid, lenmid - len + 1, lenmid);
// if (my == STD) ans++;
// }
// return ans;
//}
int check(int mid) {
int ans = 0;
ull STD = get_has(mid, 1, length[mid]);//所要匹配的后缀的哈希值
for (int i = 1; i <= n; i++) {//直接O(n)找
if (a[i] > a[mid])continue;
if (length[i] < length[mid]) continue;
ull my = get_has(i, length[i] - length[mid] + 1, length[i]);
if (my == STD) ans++;
}
return ans;
}
int main() {
p[0] = 1;
for (int i = 1; i <= 1005; i++) p[i] = p[i - 1] * seed;//预处理
scanf("%d", &T);
while (T--) {
// P.clear();
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++) {
int val;
scanf("%s%d", str[i] + 1, &val);
length[i] = strlen(str[i] + 1);
Hash(i, str[i]);
a[i] = val;
// P.insert(make_pair(val, i));
}
scanf("%d", &q);
while (q--) {
int op, x, y;
scanf("%d", &op);
if (op == 1) {
scanf("%d%d", &x, &y);
// P.erase(make_pair(a[x], x));
a[x] = y;
// P.insert(make_pair(a[x], x));
} else {
scanf("%d", &x);
int ans = check(x);
printf("%d\n", ans);
}
}
}
return 0;
}
字典树+树状数组
大佬的思想:
思路:把字符串反转就转化为前缀的问题,这样就可以用字典树来处理是否为前缀问题
然后再用树状数组来维护数量 ,具体做法是把字符串按长度从小到大插入字典树 ,对于第i次插入, 若某个结点是结尾结点,则将该点的树状数组更新 ,因为以该点结尾的字符串是当前插入字符串的前缀 ,查询操作就是查询第k个字符串结尾的点的树状数组1到w[k]的和 ,修改操作同样和插入操作类似,把路径上结尾结点的所有树状数组更新 ,由于只有n个字符串,开n颗树状数组就够了
查询操作O(logN)
修改操作O(len logN)
总复杂度上界O(T * q * len * logN)
#include <cmath>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#include <stack>
#include <set>
#include <map>
#include <cctype>
#define eps 1e-8
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define lson l,mid,rt<<1
#define rson mid+1,r,(rt<<1)+1
#define CLR(x,y) memset((x),y,sizeof(x))
#define fuck(x) cerr << #x << "=" << x << endl
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int seed = 131;
const int maxn = 1000 + 5;
const int mod = 998244353;
int T, n, q, pos;
int bit[maxn][maxn];//bit[i][j]第i种字符串,[1,j]的和
int trie[maxn][27], End[maxn]; //End[k]=p记录第k个字符串结尾是属于第p个树状数组
int length[maxn], w[maxn]; //length每个字符串的长度,w是权值
int id[maxn]; //id[root]=num,表示当前节点root是第num个字符串的结尾
char str[maxn][maxn];//记录每一个字符串
struct node {
int id, len;
} e[maxn];
bool cmp(node a, node b) {
return a.len < b.len;
}
int lowbit(int x) {
return x & (-x);
}
void add(int k, int x, int num) { //第k个,在权值为w的上面加上num
while (x <= 1000) {
bit[k][x] += num;
x += lowbit(x);
}
}
int sum(int k, int x) { //求第k个树状数组,前x项的和
int ans = 0;
while (x) {
ans += bit[k][x];
x -= lowbit(x);
}
return ans;
}
void Insert(int k, char *s) { //插入第k个字符串
int root = 0;
for (int i = length[k]; i >= 1; i--) {
if (trie[root][s[i] - 'a'] == 0) {
trie[root][s[i] - 'a'] = pos++;
}
root = trie[root][s[i] - 'a'];
if (id[root]) {//因为是要求后缀相同,那么长度长的字符串对短的有影响
add(id[root], w[k], 1);
}
}
if (id[root] == 0) {
id[root] = k;
add(id[root], w[k], 1);
}
End[k] = id[root];//这么写是因为可能有重复
}
void updata(int k, int v, char *s) {
int root = 0;
for (int i = length[k]; i >= 1; i--) {
root = trie[root][s[i] - 'a'];
if (id[root]) {
add(id[root], w[k], -1);//修改操作相当于减去原来的在加上更新的
add(id[root], v, 1);
}
}
w[k] = v;
}
int main() {
scanf("%d", &T);
while (T--) {
pos = 1;
CLR(id, 0);
CLR(End, 0);
CLR(trie, 0);
CLR(bit, 0);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%s%d", str[i] + 1, &w[i]);
length[i] = strlen(str[i] + 1);
e[i].id = i;
e[i].len = length[i];
}
sort(e + 1, e + 1 + n, cmp);
for (int i = 1; i <= n; i++) {
Insert(e[i].id, str[e[i].id]);
}
scanf("%d", &q);
while (q--) {
int op, k, x;
scanf("%d", &op);
if (op == 2) {
scanf("%d", &k);
int ans = sum(End[k], w[k]);
printf("%d\n", ans);
} else {
scanf("%d%d", &k, &x);
updata(k, x, str[k]);
}
}
}
return 0;
}