1. 程式人生 > 實用技巧 >P3065 [USACO12DEC]First! G TRIE樹+拓撲排序

P3065 [USACO12DEC]First! G TRIE樹+拓撲排序

題意:

給定\(n\)個單詞,求在自定義字典序的情況下,多少個單詞的字典序最小,並按順序輸出

範圍&性質:\(1\le n\le 3\times 10^4\),每個單詞長度不超過20

分析:

暴力做法:

可知,對於一個固定的字典序,有且僅有一個單詞字典序最小(不考慮完全相同),所以樸素的做法有了,列舉字典序,在TRIE樹上找出對應的字串,複雜度\(O(26!\times20)\)(字典序數目\(\times\)TRIE樹深度)

正解:

假定每一個單詞都可以作為最小字典序,對於每個單詞按照形成的字典序(上面提到字典序和單詞一一對應),由字典序小的字母向大的字母連邊,若存在環則假設不成立。複雜度為\(O(26*nm)\)

tips:

  1. 判環可以利用拓撲排序,最後若存在度數不為0的點,即存在環
  2. 對於字首相同的字串,大的字典序不可能最小,直接判斷就好

程式碼:

#include<bits/stdc++.h>

using namespace std;

namespace zzc
{
	const int maxn = 3e5+5;
	int n,cnt=1,sum=0;
	int son[maxn][26],rd[30],ans[maxn];
	char ch[maxn][30];
	bool vis[maxn*10];
	
	struct edge
	{
		int to,nxt;
	}e[905];
	
	int ecnt=0;
	int head[30];
	void add(int u,int v)
	{
		e[++ecnt].to=v;
		e[ecnt].nxt=head[u];
		rd[v]++;
		head[u]=ecnt;
	}
	
	void insert(int x)
	{
		int len=strlen(ch[x]+1),now=1;
		for(int i=1;i<=len;i++)
		{
			int tmp=ch[x][i]-'a';
			if(!son[now][tmp]) son[now][tmp]=++cnt;
			now=son[now][tmp];
		}
		vis[now]=true;
	}
	
	bool check(int x)
	{
		int len=strlen(ch[x]+1),now=1;
		for(int i=1;i<=len;i++)
		{
			int tmp=ch[x][i]-'a';
			if(vis[now]) return false;
			for(int i=0;i<26;i++)
			{
				if(tmp!=i&&son[now][i])
				{
					add(tmp,i);
				}
			}
			now=son[now][tmp];
		}
		return true;
	}
	
	bool topo()
	{
		queue<int> q;
		for(int i=0;i<26;i++)
		{
			if(!rd[i])
			{
				q.push(i);
			}
		}
		while(!q.empty())
        {
        	int u=q.front();q.pop();
        	for(int i=head[u];i;i=e[i].nxt)
        	{
        		int v=e[i].to;
        		if(rd[v])
        		{
        			if(--rd[v]==0)
        			{
        				q.push(v);
					}
				}
			}
		}
		for(int i=0;i<26;i++)
		{
			if(rd[i]) return false;
		}
		return true;
	}
	
	void work()
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%s",ch[i]+1);
			insert(i);
		}
		for(int i=1;i<=n;i++)
		{
			memset(rd,0,sizeof(rd));
			memset(head,0,sizeof(head));
			ecnt=0;
			if(check(i)&&topo())
			{
				ans[++sum]=i;
			}
		}
		printf("%d\n",sum);
		for(int i=1;i<=sum;i++)
		{
			printf("%s\n",ch[ans[i]]+1);
		}
	}
	
}

int main()
{
	zzc::work();
	return 0;
}