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

攻防演练

小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;
}

posted on 2022-08-11 12:50  autoint  阅读(321)  评论(0编辑  收藏  举报

导航