P3065 [USACO12DEC] First! G 题解

考场上一眼看出判环,但是没想到在trie树上判环,其实主要是因为只有13min了
考虑暴力思路,那么就是 \(\mathcal O(n)\) 枚举当前考虑到哪个字符串,然后再里层 \(\mathcal O(n)\) 枚举 \(\mathrm{check}\) 一下,对于每一对向后枚举直到不相等,则从向 当前要成为最小的那个串的当前字母 向 \(\mathrm{check}\) 枚举到的那个串的当前字母连边,如果被包含要判掉,判掉时注意如果是较短的不要不判环。然后统一判字符有没有环就行。时间复杂度 \(\mathcal O(|\Sigma|^2n^2 )\)

\(\mathrm{Code}\)

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,m,m1,m2,k,cnt,fl;
string ch[N];bool tag[N],vis[N],add[30][30];
struct E{
	int to,nxt;
}e[900];int hd[30],tot;
inline void Add(int u,int v){
	e[++tot].to=v;e[tot].nxt=hd[u],hd[u]=tot;
} 
inline void dfs(int nw){
	if(!fl)return;
	if(vis[nw]){fl=0;return;}
	vis[nw]=1;
	for(int i=hd[nw];i;i=e[i].nxt){
		dfs(e[i].to);
	}vis[nw]=0;
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n;for(int i=1;i<=n;++i)cin>>ch[i];
	for(int i=1;i<=n;++i){
		tag[i]=1;m1=ch[i].size();
		for(int j=1;j<=n;++j){
			if(i==j) continue;
			m2=ch[j].size();m=min(m1,m2); 
			for(k=0;k<m;++k){
				if(ch[i][k]!=ch[j][k]){
					if(!add[ch[i][k]-'a'+1][ch[j][k]-'a'+1]){
						Add(ch[i][k]-'a'+1,ch[j][k]-'a'+1);
						add[ch[i][k]-'a'+1][ch[j][k]-'a'+1]=1;
					}break;
				}
			}if(k==m){
				if(m1>m){tag[i]=0;break;}
				else tag[i]=1;
			}
		}if(tag[i]){
			fl=1;
			for(int j=1;j<=26;++j){
				dfs(j);if(!fl)break;
			}tag[i]=fl;
		}for(int j=1;j<=26;++j)hd[j]=0,vis[j]=0;
		for(int j=1;j<=tot;++j)e[j].to=e[j].nxt=0;tot=0;
		for(int j=1;j<=26;++j){
			for(int k=1;k<=26;++k)add[j][k]=0;
		}
	}for(int i=1;i<=n;++i){
		if(tag[i])++cnt;
	}cout<<cnt<<"\n";
	for(int i=1;i<=n;++i){
		if(tag[i])cout<<ch[i]<<"\n";
	}
	return 0;
} 

正解其实就是把所有串放到Trie上判环。
那么我们不难想到对于每个串的第 \(i\) 个字母,是在第 \(i\) 层的,那么其实就是我们要让这个字母在每一层中最小。
那么为什么这样复杂度就对了呢,因为我们比较的是前缀信息,时间复杂度瓶颈实际在于每次匹配都丢弃了原来的信息。我们每次不需要重建Trie树,每次用到Trie树的深度是很小的,边的条数就比较小,避免了重复,实际上严格复杂度应该是 \(\mathcal O(|\Sigma|^2 n)\) 的,因为边数最多是 \(\mathcal O(|\Sigma|^2)\) 的。

\(\mathrm{Code}\)

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,fl,deg[30];
int tr[N][30],idx=1,ans;
bool ed[N],book[N],e[30][30];
vector<int> g[30];
inline int cal(char x){return x-'a'+1;}
inline int read(){
	char ch;int x=0,f=1;
	while(!isdigit(ch=getchar())){if(ch=='-')f=-1;}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void insert(string s){
	int nw=1,m=s.size(),k;
	for(int j=0;j<m;++j){
		k=cal(s[j]);
		if(!tr[nw][k])tr[nw][k]=++idx;
		nw=tr[nw][k];
	}ed[nw]=1; 
	return;
}
inline void topo(){
	queue<int> q;int h;
	for(int i=1;i<=26;++i) if(!deg[i]) q.push(i);
	while(!q.empty()){
		h=q.front(),q.pop();
		for(int i:g[h]){
			if(--deg[i]==0)q.push(i);
		}
	}return;
}
inline bool chk(string x){
	int u=1,v,m=x.size();
	for(int i=1;i<=26;++i)g[i].clear();
	memset(deg,0,sizeof(deg));
	memset(e,0,sizeof(e));
	for(int i=0;i<m;++i){
		if(ed[u])return false;//是前缀 
		v=cal(x[i]);
		for(int j=1;j<=26;++j){
			if(v!=j&&tr[u][j]&&!e[v][j]){//同层连边 
				e[v][j]=1,++deg[j];
				g[v].push_back(j);
//				cout<<id<<" "<<v<<" "<<j<<"\n";
			}
		}u=tr[u][v];//向下一层走 
	}topo();
	for(int i=1;i<=26;++i){
		if(deg[i])return false;
	}return true;
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n;string s[N];
	for(int i=1;i<=n;++i){
		cin>>s[i];insert(s[i]);
	}for(int i=1;i<=n;++i){
		ans+=book[i]=chk(s[i]);
	}cout<<ans<<"\n";
	for(int i=1;i<=n;++i){
		if(book[i])cout<<s[i]<<"\n";
	}return 0;
}

不会有人像我一样关了流同步还getchar吧,被硬控一个晚自习

posted @ 2024-11-19 15:41  mountzhu  阅读(22)  评论(0)    收藏  举报