BZOJ 2754 [SCOI2012]喵星球上的点名 (AC自动机、树状数组)
吐槽: 為啥很多人用AC自動機暴力跳都過了?復雜度真的對么?
做法一: AC自動機+樹狀數組
姓名的問題,中間加個特殊字符連起來即可。
肯定是對點名串建AC自動機(map存兒子),然后第一問就相當于問每個姓名串(以下稱作“關鍵路徑”)經過了多少個點名串(以下稱做“關鍵點”)在fail樹中的子樹中的至少一點,第二問就相當于問你每條關鍵路徑被多少個關鍵點經過了在fail樹的子樹中至少一個點。
所以對于每個關鍵路徑在AC自動機上跑,每跑到一個點把它到根的路徑上打上標記(注意每個姓名串要有不同的標記),最后統計標記個數即可。
然后如果暴力跳去更新可過,我不知道復雜度對不對,感覺是錯的。(也許是\(O(n\sqrt n)\)?)
腦子里第一思路是用bitset, 可以在暴力的復雜度基礎上除以\(\omega\), 沒試過
第二思路是線段樹/啟發式合并,沒仔細想
最后看了一波題解: 我們的目標就是讓同一關鍵路徑上的點只被加一次,這樣就可以變或為加,不需要狀壓bitset了。
然后一種好辦法是像虛樹那樣按DFS序排序,相鄰兩個求出LCA,在LCA到根的路徑上-1. 差分樹狀數組維護即可。
時間復雜度\(O(n\log n)\)
易錯點: 注意AC自動機不補成Trie圖,絕對不能再fail[son[u][i]]=son[fail[u]][i]了!(一晚上的慘痛教訓……) 即使是寫板子也要經過腦子!!!!!!!
聽說也可以SA+主席樹?并不會233
代碼
#include<cstdio> #include<cstdlib> #include<cstring> #include<vector> #include<map> #include<algorithm> using namespace std;const int N = 5e4; const int S = 5e4+1; const int SIZ = 1e5; const int LGSIZ = 16; struct Edge {int v,nxt; } e[(SIZ<<1)+3]; int fe[SIZ+3]; vector<int> a[N+3]; vector<int> b[N+3]; vector<int> ky; map<int,int> son[SIZ+3]; int fail[SIZ+3]; int id[N+3]; int tr[SIZ+3]; int que[SIZ+3]; int ansa[N+3],ansb[N+3]; int dfn[SIZ+3]; int fa[SIZ+3][LGSIZ+3]; int sz[SIZ+3]; int dep[SIZ+3]; int num[SIZ+3]; int n,m,siz,en,cnt;void addval(int lrb,int val) {while(lrb<=cnt){tr[lrb] += val;lrb += (lrb&(-lrb));} }int querysum(int rb) {int ret = 0;while(rb>0){ret += tr[rb];rb -= (rb&(-rb));}return ret; }int insertstr(vector<int> str) {int u = 0;for(int i=0; i<str.size(); i++){if(son[u].count(str[i])==0) {siz++; son[u][str[i]] = siz;}u = son[u][str[i]];}num[u]++;return u; }void buildACA() {int head = 1,tail = 0;for(map<int,int>::iterator iter=son[0].begin(); iter!=son[0].end(); iter++){int u = (iter->second);tail++; que[tail] = u; fail[u] = 0;}while(head<=tail){int u = que[head]; head++;for(map<int,int>::iterator iter=son[u].begin(); iter!=son[u].end(); iter++){int v = (iter->second),i = (iter->first);if(v){int uu = fail[u];while(uu && !son[uu].count(i)) {uu = fail[uu];}fail[v] = son[uu][i];tail++; que[tail] = v;}}} }void addedge(int u,int v) {en++; e[en].v = v;e[en].nxt = fe[u]; fe[u] = en; }void dfs(int u) {cnt++; dfn[u] = cnt;sz[u] = 1;for(int i=1; i<=LGSIZ; i++) fa[u][i] = fa[fa[u][i-1]][i-1];for(int i=fe[u]; i; i=e[i].nxt){if(e[i].v==fa[u][0]) continue;fa[e[i].v][0] = u;dep[e[i].v] = dep[u]+1;num[e[i].v] += num[u];dfs(e[i].v);sz[u] += sz[e[i].v];} }int LCA(int u,int v) {if(dep[u]<dep[v]) swap(u,v);int dif = dep[u]-dep[v];for(int i=LGSIZ; i>=0; i--) {if(dif&(1<<i)) {u = fa[u][i];}}if(u==v) return u;for(int i=LGSIZ; i>=0; i--) {if(fa[u][i]!=fa[v][i]) {u = fa[u][i]; v = fa[v][i];}}return fa[u][0]; }bool cmp_dfn(int x,int y) {return dfn[x]<dfn[y];}int main() {scanf("%d%d",&n,&m); siz = 0;for(int i=1; i<=n; i++){int len; scanf("%d",&len); for(int j=1; j<=len; j++) {int x; scanf("%d",&x); a[i].push_back(x);}a[i].push_back(S);scanf("%d",&len); for(int j=1; j<=len; j++) {int x; scanf("%d",&x); a[i].push_back(x);}}for(int i=1; i<=m; i++){int len; scanf("%d",&len); for(int j=1; j<=len; j++) {int x; scanf("%d",&x); b[i].push_back(x);}id[i] = insertstr(b[i]);}buildACA();for(int i=1; i<=siz; i++){addedge(fail[i],i); addedge(i,fail[i]);}cnt = 0; dfs(0);for(int i=1; i<=n; i++){int u = 0; ky.push_back(u);for(int j=0; j<a[i].size(); j++){while(u && !son[u][a[i][j]]) {u = fail[u];}u = son[u][a[i][j]]; if(u) ky.push_back(u);}sort(ky.begin(),ky.end(),cmp_dfn);for(int j=0; j<ky.size(); j++){if(j>0){int lca = LCA(ky[j],ky[j-1]);addval(dfn[lca],-1); ansa[i] -= num[lca];}addval(dfn[ky[j]],1); ansa[i] += num[ky[j]];}ky.clear();}for(int i=1; i<=m; i++){ansb[i] = querysum(dfn[id[i]]+sz[id[i]]-1)-querysum(dfn[id[i]]-1);}for(int i=1; i<=m; i++) printf("%d\n",ansb[i]);for(int i=1; i<=n; i++) printf("%d ",ansa[i]);return 0; }總結
以上是生活随笔為你收集整理的BZOJ 2754 [SCOI2012]喵星球上的点名 (AC自动机、树状数组)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BZOJ 4327 [JSOI2012]
- 下一篇: BZOJ 2434 Luogu P241