• 十二省联考2019 字符串问题


    字符串问题

    题目背景

    Yazid 和Tiffany 喜欢字符串问题。在这里,我们将给你介绍一些关于字符串的基本概念。

    对于一个字符串 $S$ ,我们定义 $|S|$ 表示 $S$ 的长度。

    接着,我们定义该串的子串 $S(L, R)$ 表示由 $S$ 中从左往右数,第 $L$ 个字符到第 $R$ 个字符依次连接形成的字符串,特别地,如果 $L < 1$ 或 $R > |S|$ 或 $L > R$,则 $S(L, R)$ 表示空串。

    我们说两个字符串相等,当且仅当它们的长度相等,且从左至右各位上的字符依次 相同。

    我们说一个字符串 $T$ 是 $S$ 的前缀,当且仅当 $S(1, |T|) = T$。

    两个字符串 $S$, $T$ 相加 $S + T$ 表示的是在 $S$ 后紧挨着写下 $T$ 得到的长度为 $|S| + |T|$ 的字符串。

    题目描述

    现有一个字符串 $S$。

    Tiffany 将从中划出 $n_a$ 个子串作为 $A$ 类串,第 $i$ 个($1 leqslant i leqslant n_a$)为 $A_i = S(la_i, ra_i)$。

    类似地,Yazid 将划出 $n_b$ 个子串作为 $B$ 类串,第 $i$ 个($1 leqslant i leqslant n_b$)为 $B_i = S(lb_i, rb_i)$。

    现额外给定 $m$ 组支配关系,每组支配关系 $(x, y)$ 描述了第 $x$ 个 $A$ 类串支配. 第 $y$ 个 $B$ 类串。

    求一个长度最大的目标串 $T$,使得存在一个串 $T$ 的分割 $T = t_1+t_2+· · ·+t_k$($k geqslant 0$)满足:

    • 分割中的每个串 $t_i$ 均为 $A$ 类串:即存在一个与其相等的 $A$ 类串,不妨假设其为 $t_i = A_{id_i}$。
    • 对于分割中所有相邻的串 $t_i, t_{i+1}$($1 leqslant i < k$),都有存在一个$A_{id_i}$ 支配的 $B$ 类串,使得该 $B$ 类串为 $t_{i+1}$ 的前缀。

    方便起见,你只需要输出这个最大的长度即可。

    特别地,如果存在无限长的目标串(即对于任意一个正整数 $n$,都存在一个满足限制的长度超过 $n$ 的串),请输出 $-1$。

    输入输出格式

    输入格式:

    单个测试点中包含多组数据,输入的第一行包含一个非负整数 $T$ 表示数据组数。接下来依次描述每组数据,对于每组数据:

    • 第 $1$ 行一个只包含小写字母的字符串 $S$。
    • 第 $2$ 行一个非负整数 $n_a$,表示 $A$ 类串的数目。接下来 $n_a$ 行,每行 $2$ 个用空格隔开的整数。
      • 这部分中第 $i$ 行的两个数分别为 $la_i$, $ra_i$,描述第 $i$ 个 $A$ 类串。
      • 保证 $1 leqslant la_i leqslant ra_i leqslant |S|$。
    • 接下来一行一个非负整数 $n_b$,表示 $B$ 类串的数目。接下来 $n_b$ 行,每行 $2$ 个用空格隔开的整数。
      • 这部分中第 $i$ 行的两个数分别为 $lb_i$, $rb_i$,描述第 $i$ 个 $B$ 类串。
      • 保证 $1 leqslant lb_i leqslant rb_i leqslant |S|$。
    • 接下来一行一个非负整数 $m$,表示支配关系的组数。接下来 $m$ 行,每行 $2$ 个用空格隔开的整数。
      • 这部分中每行的两个整数 $x$, $y$,描述一对 $(x, y)$ 的支配关系,具体意义见 【题目描述】。
      • 保证 $1 leqslant x leqslant n_a$,$1 leqslant y leqslant n_b$。保证所有支配关系两两不同,即不存在两组支配关系的 $x, $y 相同。

    输出格式:

    依次输出每组数据的答案,对于每组数据:

    • 一行一个整数表示最大串长。特别地,如果满足限制的串可以是无限长的,则请 输出 $-1$。

    输入输出样例

    输入样例#1: 复制
    3
    abaaaba
    2
    4 7
    1 3
    1
    3 4
    1
    2 1
    abaaaba
    2
    4 7
    1 3
    1
    7 7
    1
    2 1
    abbaabbaab
    4
    1 5
    4 7
    6 9
    8 10
    3
    1 6
    10 10
    4 6
    5
    1 2
    1 3
    2 1
    3 3
    4 1
    输出样例#1: 复制
    7
    -1
    13

    说明

    样例一解释

    对于第 $1$ 组数据,$A$ 类串有 $ exttt{aaba}$ 与 $ exttt{aba}$,$B$ 类串有 $ exttt{aa}$,且 $A_2$ 支配 $B_1$。我们可以找到串 $ exttt{abaaaba}$,它可以拆分成 $A_2 + A_1$,且 $A_1$ 包含由 $A_2$ 所支配的 $B_1$ 作为前缀。可以证明不存在长度更大的满足限制的串。

    对于第 $2$ 组数据,与第 $1$ 组数据唯一不同的是,唯一的 $B$ 类串为 $ exttt{a}$。容易证明存在无限长的满足限制的串。

    对于第 $3$ 组数据,容易证明 $ exttt{abbaabbaaaabb}$ 是最长的满足限制的串。

    子任务

    img

    为了方便你的阅读,我们把测试点编号放在了表格的中间,请你注意这一点。

    表格中的 $|A_i| > |B_j|$ 指的是任意 $B$ 类串的长度不超过任意 $A$ 类串的长度。

    对于所有测试点,保证:$T leqslant 100$,且对于测试点内所有数据,$|S|$, $n_a$, $n_b$, $m$ 的总和分别不会超过该测试点中对应单组数据的限制的 $10$ 倍。比如,对于第 $1$ 组测试点,就有 $sum n_a leqslant 10 imes 100 = 1000$ 等。特别地,我们规定对于测试点 $4$,有 $T leqslant 10$。

    对于所有测试点中的每一组数据,保证:$1 leqslant |S| leqslant 2 imes 10^5$,$n_a$, $n_b leqslant 2 imes 10^5$,$m leqslant 2 imes 10^5$

    提示

    十二省联考命题组温馨提醒您:

    数据千万条,清空第一条。

    多测不清空,爆零两行泪。

    题解

    参照MangoyangChm_wt的题解。

    考虑问题转化为一个A串向其支配的所有B串的后缀A串连边,如果有环答案 −1;否则是这个 DAG 上最长路径。直接建图是(n^2)的,考虑优化建图即可。

    由于 A,B 都是原串的一个子串,那么对原串的反串建 SAM,一个子串的后缀就是其所在节点上比它长的串以及,其子树里的所有串。

    首先将所有 A,B 串在 SAM上用倍增定位并新建节点,把SAM上每个节点拆成入点和出点,对于SAM每一个节点上的 A,B 串分节点按照长度排序和入点出点连成一条链,每个出点再向其孩子入点连边。此时直接让 A 串对应点和 B 串对应分节点点连边即可。

    但是有一个问题,那就是这样连边的话可以在SAM的一个节点上走,从len小的A走到len大的B,还计入了A的贡献,这样就错了。考虑要取用一个点的贡献那就不能再往同一个SAM节点的len更大的分节点上走了。所以不要把 A 串的贡献直接记在链上的节点,再新建一个节点记在这个节点上面然后让原来的节点连一条边到它,然后由它来连支配边即可。

    总复杂度(O(nlog n)),瓶颈在于排序连边的部分。

    #include<bits/stdc++.h>
    #define rg register
    #define il inline
    #define co const
    template<class T>il T read(){
        rg T data=0,w=1;rg char ch=getchar();
        for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
        for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
        return data*w;
    }
    template<class T>il T read(rg T&x) {return x=read<T>();}
    typedef long long ll;
    using namespace std;
    typedef pair<int,int> pii;
    
    co int N=2e6;
    int na,nb;
    namespace graph{
    	queue<int> q;
    	vector<int> g[N];
    	int deg[N],n;ll val[N],dis[N];
    	il void add_edge(int x,int y){
    		g[x].push_back(y),++deg[y],n=max(n,max(x,y));
    	}
    	void solve(){
    		for(int i=1;i<=n;++i)if(!deg[i]) q.push(i);
    		int bfn=0;
    		for(int u;!q.empty();q.pop()){
    			u=q.front(),dis[u]+=val[u],++bfn;
    			for(int i=0,v;i<g[u].size();++i){
    				v=g[u][i],dis[v]=max(dis[v],dis[u]);
    				if(!--deg[v]) q.push(v);
    			}
    		}
    		if(bfn!=n) return puts("-1"),void();
    		ll ans=0;
    		for(int i=1;i<=n;++i) ans=max(ans,dis[i]);
    		printf("%lld
    ",ans);
    	}
    	void clear(){
    		for(int i=1;i<=n;++i) deg[i]=dis[i]=val[i]=0,g[i].clear();
    		n=0;
    	}
    }
    il bool cmp(co pii&a,co pii&b){
    	return a.first!=b.first?a.first<b.first:a.second>b.second;
    }
    namespace SAM{
    	int last=1,tot=1;	
    	int ch[N][26],fa[N],len[N],ref[N];
    	void ins(int c,int po){
    		int p=last,cur=last=++tot;
    		len[cur]=len[p]+1,ref[po]=cur;
    		for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
    		if(!p) fa[cur]=1;
    		else{
    			int q=ch[p][c];
    			if(len[q]==len[p]+1) fa[cur]=q;
    			else{
    				int clone=++tot;
    				memcpy(ch[clone],ch[q],sizeof ch[q]);
    				fa[clone]=fa[q],len[clone]=len[p]+1;
    				fa[cur]=fa[q]=clone;
    				for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
    			}
    		}
    	}
    	int anc[N][19];
    	vector<int> e[N];
    	void dfs(int u){
    		anc[u][0]=fa[u];
    		for(int i=1;i<=18;++i) anc[u][i]=anc[anc[u][i-1]][i-1];
    		for(int i=0;i<e[u].size();++i)
    			graph::add_edge(u+tot,e[u][i]),dfs(e[u][i]);
    	}
    	void build_tree(){
    		for(int i=2;i<=tot;++i) e[fa[i]].push_back(i);
    		dfs(1);
    	}
    	vector<pii> s[N];
    	void push_node(int l,int r,int id){
    		int x=ref[l];
    		for(int i=18;i>=0;--i)
    			if(len[anc[x][i]]>=r-l+1) x=anc[x][i];
    		s[x].push_back(pii(r-l+1,id));
    	}
    	void build_graph(){
    		for(int i=1;i<=tot;++i){
    			sort(s[i].begin(),s[i].end(),cmp);
    			for(int j=0,x;j<s[i].size();++j){
    				x=s[i][j].second;
    				if(j==0) graph::add_edge(i,x+tot*2);
    				if(j==s[i].size()-1) graph::add_edge(x+tot*2,i+tot);
    				else graph::add_edge(x+tot*2,s[i][j+1].second+tot*2);
    				if(x<=na){
    					graph::add_edge(x+tot*2,x+tot*2+na+nb);
    					graph::val[x+tot*2+na+nb]=s[i][j].first;
    				}
    			}
    			if(!s[i].size()) graph::add_edge(i,i+tot);
    		}
    	}
    	void clear(){
    		for(int i=1;i<=tot;++i) memset(ch[i],0,sizeof ch[i]),e[i].clear(),s[i].clear();
    		last=tot=1;
    	}
    }
    
    char str[N];
    void real_main(){
    	scanf("%s",str+1);int n=strlen(str+1);
    	for(int i=n;i;--i) SAM::ins(str[i]-'a',i);
    	SAM::build_tree();
    	read(na);
    	for(int i=1,l,r;i<=na;++i) read(l),read(r),SAM::push_node(l,r,i);
    	read(nb);
    	for(int i=1,l,r;i<=nb;++i) read(l),read(r),SAM::push_node(l,r,i+na);
    	SAM::build_graph();
    	int m=read<int>();
    	for(int i=1,x,y;i<=m;++i) read(x),read(y),graph::add_edge(x+SAM::tot*2+na+nb,y+na+SAM::tot*2);
    	graph::solve();
    	SAM::clear(),graph::clear();
    }
    int main(){
    	for(int T=read<int>();T--;) real_main();
    	return 0;
    }
    
  • 相关阅读:
    两种存储思路
    越来越浅
    我了解的前端史
    关于称赞小孩
    怎么写递归
    Python笔记(十八):协程asyncio
    网络协议笔记(一):HTTP协议基础知识
    Linux笔记(三):常用命令
    算法笔记(九):二分查找
    数据结构笔记(二):栈、队列
  • 原文地址:https://www.cnblogs.com/autoint/p/10898656.html
Copyright © 2020-2023  润新知