ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CCPC2021 女生专场 攻防演练 和 test20200528 要换换名字

2022-08-11 13:03:04  阅读:186  来源: 互联网

标签:CCPC2021 攻防 26 le int test20200528 nx 名字 que


攻防演练

小Q和小C在比特公司的系统中进行攻防演练,这个系统经过特殊设定,只能接收任何只含有前 \(m\) 个小写英文字母的非空字符串作为输入命令。

小Q事先准备了一个长为 \(n\) 的字符串 \(s = s_1 s_2 \ldots s_n\),为了能够在演练时输入到系统中,这个字符串只会包含前 \(m\) 个小写英文字母。攻防演练一共进行 \(q\) 轮,每轮开始时小Q会选择一个 \(s\) 的非空子串 \(s_{l,r} = s_l s_{l+1} \ldots s_r\) 输入到系统中作为本轮演练的防火墙规则。当防火墙规则配置完成之后,对于任何输入到系统中的命令 \(c\),只要 \(c\) 是 \(s_{l,r}\) 的子序列就会被防火墙拦截。

小C的任务是在每轮演练中,在小Q完成本轮防火墙规则的配置之后,输入一个不被防火墙拦截的命令。为了节约时间,小C想知道每轮演练需要输入的最短字符串的长度是多少。也就是说,小C想找到最小的正整数 \(k\),存在一个长为 \(k\) 的字符串 \(t = t_1 t_2 \ldots t_k\),使得不存在任意一个长为 \(k\) 的序列 \(p_1, p_2, \ldots p_k\) 满足 \(l \le p_1 < p_2 < \ldots < p_k \le r\) 且对每个 \(i=1,2,\ldots,k\) 均有 \(t_i = s_{p_i}\)。

\(1 \le m \le 26, 1 \le n \le 200\,000, 1 \le q \le 200\,000\)

题解

我的第一个想法是答案不会太大,因为长度为\(l\)的子序列种类数最多是\(\binom{n}{l}\),而长度为\(l\)的字符串总数是\(m^l\),只需要建出子序列自动机然后对于询问\([l, r]\)从结点\(l-1\)开始暴力BFS就行了。

然后我想了想他怎么卡我,发现只需要构造形如abcd...zabcd...zabcd...z...的字符串,即26个字母各出现一次然后循环就行了。这样答案会达到\(n/m\)级别。

但是这就是关键,那就是只有26个字母都出现一次后答案才能加一。

那么可以以此设计跳跃关系。每个结点往后跳到之后26个字母最近出现位置的最远者。

使用倍增优化时间复杂度,\(O(nm+(n+q)\log n)\)。

constexpr int N=2e5+10;
char s[N];
int nx[N][26], to[N][18];

int main(){
    int m=read<int>(), n=read<int>();
    scanf("%s", s+1);
    fill(nx[n], nx[n]+m, n+1);
    for(int i=n-1; i>=0; --i){
        copy(nx[i+1], nx[i+1]+m, nx[i]);
        nx[i][s[i+1]-'a']=i+1;
    }
    for(int i=n; i>=0; --i){
        int mx=0;
        for(int j=0; j<m; ++j) mx=max(mx, nx[i][j]);
        to[i][0]=mx;
        // cerr<<i<<" to="<<to[i][0]<<endl;
        for(int j=1; j<=17; ++j) to[i][j]=to[to[i][j-1]][j-1];
    }
    for(int q=read<int>(); q--; ){
        int l=read<int>(), r=read<int>();
        int p=l-1, ans=0;
        for(int i=17; i>=0; --i)
            if(to[p][i] && to[p][i]<=r) p=to[p][i], ans+=1<<i;
        printf("%d\n", ans+1);
    }
    return 0;
}

要换换名字

从前有\(n\)个小朋友,他们有着令人印象深刻的很长的名字,比如cqqqqqqwqakioi。拥有很长的名字固然是一件振奋人心的大好事,但名字太长不方便被人膜拜,所以他们决定要换换名字。

这些小朋友很喜欢自己原来的名字,所以希望新名字是原来名字的非空子序列。例如,jiangkangping可以把名字改为jkp。

同时,两个名字相同的小朋友是非常令人困惑的,所以这些新名字必须两两不同。

请你为小朋友们换换名字,使得新名字中最长的尽量短。

请注意:虽然新名字要求两两不同,但旧名字可能相同。新名字也可以与旧名字相同。

对于100%的数据,\(2\leq n\leq 300\),\(1\leq \text{length}\leq 300\)。

题解

先二分答案\(\text{mid}\)。

然后我们只要对于每个小朋友都能找到\(n\)个\(\leq \text{mid}\)的子序列的话,就一定能够匹配上。证明考虑Hall定理。

找子序列可以在子序列自动机上BFS。

这样一来,只需要进行\(O(\log n)\)次点数为\(n^2\)的二分图匹配。时间复杂度\(O(n^3\log n)\)。

CO int N=310,inf=1e9;
int n,a[N][N],len[N];
int ans[N];
string plan[N];

namespace Trie{
	int ch[N*N][26],dep[N*N],tot=1;
	vector<int> v[N*N];
	string s;
	
	int insert(int x,int c){
		if(!ch[x][c]){
			ch[x][c]=++tot;
			dep[ch[x][c]]=dep[x]+1;
		}
		return ch[x][c];
	}
	void dfs(int x){
		for(int i:v[x]) plan[i]=s;
		for(int i=0;i<26;++i)if(ch[x][i]){
			s.push_back(i+'a');
			dfs(ch[x][i]);
			s.pop_back();
		}
	}
	void read(){
		for(int i=1;i<=n;++i) v[ans[i]].push_back(i);
		dfs(1);
	}
}

vector<int> mat[N];

namespace Match{
	int nxt[N][N][26];
	
	void bfs(int x){
		deque<pair<int,int> > que={{1,0}};
		int tot=0;
		while(que.size() and tot<=n){
			pair<int,int> p=que.front();que.pop_front();
			if(p.first>1) mat[x].push_back(p.first);
			for(int i=0;i<26;++i)
				if(nxt[x][p.second+1][i]<=len[x]){
					que.push_back({Trie::insert(p.first,i),nxt[x][p.second+1][i]});
					++tot;
				}
		}
		while(que.size()){
			mat[x].push_back(que.front().first);
			que.pop_front();
		}
	}
	void solve(){
		for(int i=1;i<=n;++i){
			fill(nxt[i][len[i]+1],nxt[i][len[i]+1]+26,len[i]+1);
			for(int j=len[i];j>=1;--j){
				copy(nxt[i][j+1],nxt[i][j+1]+26,nxt[i][j]);
				nxt[i][j][a[i][j]]=j;
			}
		}
		for(int i=1;i<=n;++i) bfs(i);
	}
}

namespace Flow{
	int S,T;
	struct edge {int y,c,a;};
	vector<edge> to[N*N];
	int dis[N*N];
	
	IN void init(int n){
		S=n-1,T=n;
		for(int i=1;i<=n;++i) to[i].clear();
	}
	IN void link(int x,int y,int c){
		to[x].push_back({y,c}),to[y].push_back({x,0});
		to[x].back().a=to[y].size()-1,to[y].back().a=to[x].size()-1;
	}
	bool bfs(){
		fill(dis+1,dis+T+1,inf),dis[S]=0;
		deque<int> que={S};
		while(que.size()){
			int x=que.front();que.pop_front();
			for(CO edge&e:to[x])if(e.c and dis[e.y]>dis[x]+1){
				dis[e.y]=dis[x]+1;
				que.push_back(e.y);
			}
		}
		return dis[T]<inf;
	}
	int dfs(int x,int lim){
		if(x==T) return lim;
		int rest=lim;
		for(edge&e:to[x])if(e.c and dis[e.y]==dis[x]+1){
			int delta=dfs(e.y,min(rest,e.c));
			if(!delta) {dis[e.y]=inf; continue;}
			rest-=delta,e.c-=delta,to[e.y][e.a].c+=delta;
			assert(to[e.y][e.a].y==x);
			if(!rest) break;
		}
		return lim-rest;
	}
	int dinic(){
		int ans=0;
		while(bfs()) ans+=dfs(S,inf);
		return ans;
	}
	int solve(int k){
		init(n+Trie::tot+2);
		for(int i=1;i<=n;++i) link(S,i,1);
		for(int i=2;i<=Trie::tot;++i)if(Trie::dep[i]<=k) link(i+n,T,1);
		for(int i=1;i<=n;++i)for(int x:mat[i])
			if(Trie::dep[x]<=k) link(i,x+n,1);
		return dinic();
	}
	void copy_ans(){
		for(int i=1;i<=n;++i)for(CO edge&e:to[i])
			if(e.c==0 and e.y>n) {ans[i]=e.y-n; break;}
	}
}

int main(){
	freopen("name.in","r",stdin),freopen("name.out","w",stdout);
	read(n);
	for(int i=1;i<=n;++i){
		static char s[N];scanf("%s",s+1);
		len[i]=strlen(s+1);
		for(int j=1;j<=len[i];++j) a[i][j]=s[j]-'a';
	}
	Match::solve();
	if(Flow::solve(300)<n){
		puts("-1");
		return 0;
	}
	int l=1,r=300;
	while(l<r){
		int mid=(l+r)>>1;
		Flow::solve(mid)>=n?r=mid:l=mid+1;
	}
	printf("%d\n",l);
	Flow::solve(l);
	Flow::copy_ans();
	Trie::read();
	for(int i=1;i<=n;++i) printf("%s\n",plan[i].c_str());
	return 0;
}

标签:CCPC2021,攻防,26,le,int,test20200528,nx,名字,que
来源: https://www.cnblogs.com/autoint/p/16575671.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有