zoukankan      html  css  js  c++  java
  • BZOJ 2754 [SCOI2012]喵星球上的点名 (AC自动机、树状数组)

    吐槽: 为啥很多人用AC自动机暴力跳都过了?复杂度真的对么?

    做法一: AC自动机+树状数组

    姓名的问题,中间加个特殊字符连起来即可。

    肯定是对点名串建AC自动机(map存儿子),然后第一问就相当于问每个姓名串(以下称作“关键路径”)经过了多少个点名串(以下称做“关键点”)在fail树中的子树中的至少一点,第二问就相当于问你每条关键路径被多少个关键点经过了在fail树的子树中至少一个点

    所以对于每个关键路径在AC自动机上跑,每跑到一个点把它到根的路径上打上标记(注意每个姓名串要有不同的标记),最后统计标记个数即可。

    然后如果暴力跳去更新可过,我不知道复杂度对不对,感觉是错的。(也许是(O(nsqrt n))?)

    脑子里第一思路是用bitset, 可以在暴力的复杂度基础上除以(omega), 没试过

    第二思路是线段树/启发式合并,没仔细想

    最后看了一波题解: 我们的目标就是让同一关键路径上的点只被加一次,这样就可以变或为加,不需要状压bitset了。

    然后一种好办法是像虚树那样按DFS序排序,相邻两个求出LCA,在LCA到根的路径上-1. 差分树状数组维护即可。

    时间复杂度(O(nlog n))

    易错点: 注意AC自动机不补成Trie图,绝对不能再fail[son[u][i]]=son[fail[u]][i]了!(一晚上的惨痛教训……) 即使是写板子也要经过脑子!!!!!!!

    听说也可以SA+主席树?并不会233

    **UPD: **BZOJ2780是本题的一部分,求每个点名串是多少姓名串的子串,SA的做法是把所有点名串连成一起然后求SA,转化成了求区间内有多少种不同的颜色。也有更简单的SAM做法,不用任何数据结构(O(n)), 对所有姓名串建广义SAM,再求出来每个点有多少个姓名串经过,然后拿点名串在上面跑,跑到结尾节点输出个数即可。

    代码

    #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
    ",ansb[i]);
    	for(int i=1; i<=n; i++) printf("%d ",ansa[i]);
    	return 0;
    }
    
  • 相关阅读:
    k6负载测试学习知识
    52条SQL语句性能优化策略(转)
    JVM学习
    jsonp劫持
    Airtest API精讲之keyevent()
    Airtest API精讲之Android自定义手势
    Airtest API精讲之text()
    Airtest之调用其他脚本——using()如何使用
    Airtest API精讲之wait(),exists()
    Airtest API精讲之报告日志log()
  • 原文地址:https://www.cnblogs.com/suncongbo/p/11054521.html
Copyright © 2011-2022 走看看