题目大意:
就是对于给出的n个字符串(n <= 10^5), 总长度不超过10^5, 每个字符串只包含字符'A', 'G', 'C', 'T', 现在又m次询问(m <= 10^5), 每次询问包含两个数a, b表示询问第a个字符串的后缀和第b个字符串的前缀最多能匹配多长
大致思路:
首先这个题可以用AC自动机或者后缀自动机来做, 我使用的是AC自动机, 将输入的所有串建立一个AC自动机, 对于每一个串A的后缀和串B的前缀进行匹配, 其实就是串A在AC自动机上结尾的点的位置沿着fail指针向上走能够走到的与串B在AC自动机上匹配时经过的位置, 那么我们对于所有B相同的询问一起处理, 也就是现将所有的AC自动机上的点依据fail指针建立fail树, 那么串B在AC自动机上匹配经过得点能对哪些点造成影响, 实际上就是经过的这些点对在fail树上的其儿子节点的影响, 这就是一个典型的利用时间戳来进行查询的线段树可以解决的问题了, 这个思想可以参考2011年成都赛区的G题 (HDU 4117), 两个题都用到了这个思想, 利用fail树来进行线段树优化
那么对于多个不同的B, 最坏情况下B有10W种, 当然我们不能对于每一个不同的B都重新还原线段树, 于是用一个小技巧, 由于只是更新区间最大值并且单点进行查询, 遇到下一组B相同的情况时更新的所有值都加上一个数, 使得一定能更新, 但是查询的时候结果小于这个数就表示是0, 这样就可以避免多次还原线段树的操作了, 总体时间复杂度O(LlogL + mlogL), L是AC自动机上的节点数, 也就和输入的字符串总长度有关
其实现在再看这题没刚开始看那么难了....之前还是造诣不够啊(一发AC真是好~)
如果用后缀自动机来做的话, fail树就相当于是parent树了
由于使用了栈外挂, 需要提交C++
代码如下:
Result : Accepted Memory : 11488 KB Time : 390 ms
/* * Author: Gatevin * Created Time: 2015/5/28 13:54:15 * File Name: Rin_Tohsaka.cpp */ #pragma comment(linker, "/STACK:16777216") #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; #define foreach(e, x) for(__typeof(x.begin()) e = x.begin(); e != x.end(); ++e) #define SHOW_MEMORY(x) cout<<sizeof(x)/(1024*1024.)<<"MB"<<endl struct Segment_Tree { #define lson l, mid, rt << 1 #define rson mid + 1, r, rt << 1 | 1 int val[100010 << 2]; bool flag[100010 << 2]; void pushUp(int rt) { val[rt] = max(val[rt << 1], val[rt << 1 | 1]); flag[rt] = 0; return; } void pushDown(int rt) { if(flag[rt]) { flag[rt << 1] = flag[rt << 1 | 1] = 1; flag[rt] = 0; val[rt << 1] = max(val[rt << 1], val[rt]); val[rt << 1 | 1] = max(val[rt << 1 | 1], val[rt]); } return; } void build(int l, int r, int rt) { if(l == r) { val[rt] = 0; flag[rt] = 0; return; } int mid = (l + r) >> 1; build(lson); build(rson); pushUp(rt); return; } //将区间[L, R]中的数比value小的更新为value, 当前区间为[l, r] void update(int l, int r, int rt, int L, int R, int value) { if(l >= L && r <= R) { val[rt] = max(val[rt], value); flag[rt] = 1; return; } pushDown(rt); int mid = (l + r) >> 1; if(mid >= L) update(lson, L, R, value); if(mid + 1 <= R) update(rson, L, R, value); } int query(int l, int r, int rt, int pos) { if(l == r) { return val[rt]; } pushDown(rt); int mid = (l + r) >> 1; if(mid >= pos) return query(lson, pos); if(mid + 1 <= pos) return query(rson, pos); return -23333333; } }; int n, m; struct ASK { int a, b, num; }; ASK ask[100010]; bool cmp(ASK ask1, ASK ask2) { if(ask1.b == ask2.b) return ask1.a < ask2.a; return ask1.b < ask2.b; } struct Trie { int next[100010][4], fail[100010]; int pos[100010];//记录每个字符串在AC自动机上的结尾位置 vector<int> G[100010];//存树 int left[100010], right[100010];//每个节点i对应的线段树的时间戳[left[i], right[i]] int L, root, time; vector <string> V; Segment_Tree seg; int newnode() { for(int i = 0; i < 4; i++) next[L][i] = -1; L++; return L - 1; } void init() { L = 0; root = newnode(); V.clear(); return; } int encode(char c) { switch(c) { case 'A': return 0; case 'G': return 1; case 'C': return 2; case 'T': return 3; } return -1; } void insert(int id) { int now = root; for(int i = 0, sz = V[id].size(); i < sz; i++) { if(next[now][encode(V[id][i])] == -1) next[now][encode(V[id][i])] = newnode(); now = next[now][encode(V[id][i])]; } pos[id] = now; return; } void build() { fail[root] = root; queue <int> Q; Q.push(root); while(!Q.empty()) { int now = Q.front(); Q.pop(); for(int i = 0; i < 4; i++) if(next[now][i] == -1) next[now][i] = now == root ? root : next[fail[now]][i]; else { fail[next[now][i]] = now == root ? root : next[fail[now]][i]; Q.push(next[now][i]); } } return; } void dfs(int now) { left[now] = ++time; for(int i = 0, sz = G[now].size(); i < sz; i++) dfs(G[now][i]); right[now] = time; } void failTree() { for(int i = 0; i < L; i++) G[i].clear(); for(int i = 1; i < L; i++) G[fail[i]].push_back(i);//建树 time = 0; dfs(root);//获取时间戳 return; } int ans[100010]; void solve() { for(int i = 0; i < m; i++) scanf("%d %d", &ask[i].a, &ask[i].b), ask[i].num = i, ask[i].a--, ask[i].b--; sort(ask, ask + m, cmp); failTree(); seg.build(1, time, 1); int base = 0; for(int i = 0; i < m; i++) { int nowb = ask[i].b; int now = root; for(int j = 0, sz = V[nowb].size(); j < sz; j++) { now = next[now][encode(V[nowb][j])]; seg.update(1, time, 1, left[now], right[now], j + 1 + base); } while(i < m && ask[i].b == nowb) { ans[ask[i].num] = max(0, seg.query(1, time, 1, left[pos[ask[i].a]]) - base); i++; } i--; base += V[nowb].size() + 1;//对于下一组相同的b, base提升一个基值, 这样就不用初始化线段树了 } for(int i = 0; i < m; i++) printf("%d\n", ans[i]); } }; Trie AC; int main() { ios::sync_with_stdio(0); while(scanf("%d %d", &n, &m) != EOF) { AC.init(); string tmp; for(int i = 0; i < n; i++) cin>> tmp, AC.V.push_back(tmp), AC.insert(i); AC.build(); AC.solve(); } return 0; }