Educational Codeforces Round 103 E题 Pattern Matching (字典树+拓扑排序)

传送门

题意

有n个模式串与m个字符串,长度均为k。要求重新排序模式串使得第 i i i个字符串依次匹配模式串,第一个被匹配的模式串排序前编号为 m t i mt_i mti
字符串均由小写字母组成,模式串由小写字母和下划线组成,下划线可以和任意字母匹配。
问:是否存在一种排序方式满足要求,若能则输出任意一种方式。

数据范围

1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1n105
1 ≤ m ≤ 1 0 5 1\leq m\leq 10^5 1m105
1 ≤ k ≤ 4 1\leq k\leq 4 1k4
1 ≤ m t i ≤ n 1\leq mt_i\leq n 1mtin

题解

很显然可以发现,第j个字符串若不能与 m t j mt_j mtj匹配,直接输出"NO",否则,若可以和 m t j , x 1 , x 2 , . . . mt_j,x_1,x_2,... mtj,x1,x2,...进行匹配,在新排列的模式串中, x 1 , x 2 . . . x_1,x_2... x1,x2...要放在 m t j mt_j mtj后面,即可以用 m t j mt_j mtj x 1 , x 2 . . . x_1,x_2... x1,x2...连边。
最终即可得到一个图。要想存在一种排序方式,该图一定要是一个有向无环图,要想得到一种排序方式,直接拓扑排序扫一下即可。
以上部分我比赛时也想到了,结果不知道怎么快速进行匹配。
注意到k最大只有4,我们可以将一个字符串(不是模式串)拆成 2 k 2^k 2k种情况,即枚举位置将部分字母替换成下划线,再将 2 k 2^k 2k个字符串放入模式串建的字典树里去跑。即可在 O ( 2 k k ) O(2^k k) O(2kk)完成匹配。
这题我一直在想如何处理模式串的下划线,没有转换思维用下划线替换字符串中的字母。
由于每个点最多只能连15条边,所以建图不用担心内存不够。时间复杂度为 O ( n k + m k 2 k ) O(nk+mk2^k) O(nk+mk2k)

代码

/*************************************************************************
	> File Name: 1.cpp
	> Author: Knowledge_llz
	> Mail: 925538513@qq.com 
	> Blog: https: https://www.cnblogs.com/Knowledge-Pig/ 
	> Created Time: 2021/2/1 15:25:34
 ************************************************************************/

#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define LL long long
#define pb push_back
#define fi first
#define se second
#define pr pair<int,int>
#define mk(a,b) make_pair(a,b)
using namespace std;
int read(){
	char x=getchar(); int u=0,fg=0;
	while(!isdigit(x)){ if(x=='-') fg=1; x=getchar(); }
	while(isdigit(x)){ u=(u<<3)+(u<<1)+(x^48); x=getchar(); }
	return fg?-u:u;
}
const int maxx=2e5+10;
int n,m,k;
char tmp[5];
struct Trie{
	int ch[maxx][30];
	int val[maxx];
	int sz;
	Trie(){ sz=1; memset(ch[0],0,sizeof(ch[0])); }
	int idx(char c){
		if(c!='_') return c-'a';
		else return 26;
	}
	void insert(char *s,int v){
		int u=0;
		For(i,0,k-1){
			int c=idx(s[i]);
			if(!ch[u][c]){
				memset(ch[sz],0,sizeof(ch[sz]));
				ch[u][c]=sz++;
			}
			u=ch[u][c];
		}
		val[u]=v;
	}
	int query(char *s,int mode){
		int u=0;
		For(i,0,k-1){
			int c=idx(s[i]);
			if(mode&1) c=26;
			mode>>=1;
			u=ch[u][c];
		}
		return val[u];
	}


}trie;
vector<int>vec[maxx],ans;
int d[maxx];
bool vis[maxx];
void dfs(int id){
	ans.pb(id);
	vis[id]=1;
	for(auto i:vec[id]){
		--d[i];
		if(!d[i]) dfs(i);
	}
}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("input.in", "r", stdin);
	freopen("output.out", "w", stdout);
#endif
	n=read(); m=read(); k=read();
	For(i,1,n){
		scanf("%s",tmp);
		trie.insert(tmp,i);
	}
	For(i,1,m){
		int mt; bool flag=0;
		scanf("%s%d",tmp,&mt);
		For(i,0,15){
			int u=trie.query(tmp,i);
			if(!u) continue;
			if(u!=mt){ vec[mt].pb(u); ++d[u]; }
			else flag=1;
		}
		if(!flag){ printf("NO\n"); return 0; }
	}
	For(i,1,n) if(!d[i] && !vis[i])  dfs(i);
	if(ans.size()!=n) printf("NO\n");
	else{
		printf("YES\n");
		for(auto i:ans) printf("%d ",i);
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值