zoukankan      html  css  js  c++  java
  • bzoj3473-字符串

    题目

    给出(n)个字符串,问每个字符串有多少个子串满足这个子串被这(n)个字符串中至少(k)个字符串包含。(一个字符串本质相同位置不同的子串算多个)。(1le kle n, sum |s|le 10^5)

    分析

    这是一个多串问题,考虑广义后缀自动机,建出广义后缀树。

    在广义后缀树上可以方便地用启发式合并set的方法得到每个点被多少个字符串包含。接着问题就变成了如何查询答案。

    直接通过set来更新答案是不现实的。

    由于一个字符串本质相同位置不同算多次,所以要求的其实就是对于一个字符串的每个前缀的最长后缀被(k)个以上的字符串包含。这可以通过把字符串放进自动机里匹配,如果当前点的被包含数小于(k)就条link,跳到一个被包含数大于等于(k)的位置,统计这个位置的(len),这是因为我们从上一个点跳过来,前缀的一个前缀被去掉,而这个点的(len)必定是上一个点的(minlen-1),所以一定还在这个串中,即我们要统计的前缀的最长后缀包含数大于等于(k)

    代码

    #include<cstdio>
    #include<iostream>
    #include<string>
    #include<vector>
    #include<cstring>
    #include<set>
    #include<algorithm>
    using namespace std;
    typedef long long giant;
    const int maxn=1e5+10;
    const int maxm=maxn<<1;
    const int maxc=26;
    string s[maxn];
    set<int> st[maxm];
    int n,k,cnt[maxm];
    struct TREE {
    	vector<int> g[maxm];
    	void add(int x,int y) {g[x].push_back(y);}
    	void merge(set<int> &a,set<int> &b) {
    		if (a.size()<b.size()) a.swap(b);
    		for (int x:b) a.insert(x);
    		b.clear();
    	}
    	void dfs(int x) {
    		for (int v:g[x]) {
    			dfs(v);
    			merge(st[x],st[v]);
    		}
    		cnt[x]=st[x].size();
    	}
    } tree;
    struct SAM {
    	int t[maxm][maxc],len[maxm],link[maxm],tot,last;
    	SAM ():tot(1),last(1) {}
    	void reset() {last=1;}
    	void add(int x,int id) {
    		if (t[last][x]) {
    			int p=t[last][x];
    			if (len[p]==len[last]+1) {
    				st[last=p].insert(id);
    				return;
    			} 
    			int q=++tot;
    			len[q]=len[last]+1;
    			memcpy(t[q],t[p],sizeof t[p]);
    			for (int j=last;j && t[j][x]==p;j=link[j]) t[j][x]=q; 
    			link[q]=link[p],link[p]=q;
    			st[last=q].insert(id);
    			return;
    		}
    		int nw=++tot,i;
    		len[nw]=len[last]+1;
    		for (i=last;i && !t[i][x];i=link[i]) t[i][x]=nw;
    		if (i) {
    			int p=t[i][x];
    			if (len[p]==len[i]+1) link[nw]=p; else {
    				int q=++tot;
    				len[q]=len[i]+1;
    				memcpy(t[q],t[p],sizeof t[p]);
    				for (int j=i;j && t[j][x]==p;j=link[j]) t[j][x]=q;
    				link[q]=link[p],link[p]=link[nw]=q;
    			}
    		} else link[nw]=1;
    		st[last=nw].insert(id);
    	}
    	void build() {
    		for (int i=2;i<=tot;++i) tree.add(link[i],i);
    	}
    	giant run(string &s) {
    		int m=s.length(),now=1;
    		giant ret=0;
    		for (int i=0;i<m;++i) {
    			char c=s[i];
    			while (now!=1 && !t[now][c-'a']) now=link[now];
    			if (t[now][c-'a']) now=t[now][c-'a'];
    			while (now!=1 && cnt[now]<k) now=link[now];
    			if (cnt[now]>=k) ret+=(giant)len[now];
    		}
    		return ret;
    	}
    } sam;	
    int main() {
    #ifndef ONLINE_JUDGE
    	freopen("test.in","r",stdin);
    #endif
    	ios::sync_with_stdio(false);
    	cin>>n>>k;
    	for (int i=1;i<=n;++i) {
    		string &str=s[i];
    		cin>>str;
    		int len=str.length();
    		sam.reset();
    		for (int j=0;j<len;++j) sam.add(str[j]-'a',i);
    	}
    	sam.build();
    	tree.dfs(1);
    	for (int i=1;i<=n;++i) cout<<sam.run(s[i])<<" ";
    	cout<<endl;
    	return 0;
    }
    
  • 相关阅读:
    关于View的onMeasure()、onSizeChanged()、onLayout()、onDraw()调用顺序
    Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
    Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
    自定义View经常重写的方法
    为自己的自定义View添加额外属性
    自定义View常用的获取宽高信息
    用工厂流水线的方式来理解 RxJava 的概念
    发布开源项目到Maven 中心仓库
    Android应用架构之Retrofit使用
    docker新手入门(基本命令以及介绍)
  • 原文地址:https://www.cnblogs.com/owenyu/p/7144408.html
Copyright © 2011-2022 走看看