Codeforces 889B (Codeforces Round #445 Div. 1) Restoration of string 图论

B. Restoration of string
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

A substring of some string is called the most frequent, if the number of its occurrences is not less than number of occurrences of any other substring.

You are given a set of strings. A string (not necessarily from this set) is called good if all elements of the set are the most frequent substrings of this string. Restore the non-empty good string with minimum length. If several such strings exist, restore lexicographically minimum string. If there are no good strings, print "NO" (without quotes).

A substring of a string is a contiguous subsequence of letters in the string. For example, "ab", "c", "abc" are substrings of string "abc", while "ac" is not a substring of that string.

The number of occurrences of a substring in a string is the number of starting positions in the string where the substring occurs. These occurrences could overlap.

String a is lexicographically smaller than string b, if a is a prefix of b, or a has a smaller letter at the first position where a and b differ.

Input

The first line contains integer n (1 ≤ n ≤ 105) — the number of strings in the set.

Each of the next n lines contains a non-empty string consisting of lowercase English letters. It is guaranteed that the strings are distinct.

The total length of the strings doesn't exceed 105.

Output

Print the non-empty good string with minimum length. If several good strings exist, print lexicographically minimum among them. Print "NO" (without quotes) if there are no good strings.

Examples
input
4
mail
ai
lru
cf
output
cfmailru
input
3
kek
preceq
cheburek
output
NO
Note

One can show that in the first sample only two good strings with minimum length exist: "cfmailru" and "mailrucf". The first string is lexicographically minimum.




给出很多字符串,要求构造一个字符串,使得所有给出的字符串在这个串当中都是出现次数最多子串。输出长度最短的答案。如果有多个答案,输出字典序最小的。


首先,答案中每个字母最多出现一次。因为如果一个字母出现多次,那么这单个的字母就是出现次数最多的子串了。

接着,我们发现答案中的字母有一定的顺序,顺序必须和所给的若干个单词中的顺序一致。要求有序,我们想到了构造有向图进行拓扑排序,把每个字母当做不同的点,最多26个点,把每个单词当中的前一个字母c1向后一个字母c2连边,表示答案当中c1必须在c2前面。

实际上,因为顺序不能矛盾,所以构造的图去掉重边之后,只能是很多条链组成的森林,不能有环和自环,否则答案就是NO。

接着就遍历所有链,把这些链构成的若干个词连起来就是答案。

需要注意的是一个串长度为1的情况,这时若这单个的字母没有连边,可能会被答案忽略。特判把它加上就好了。


#include <cstdio>
#include <iostream>
#include <string.h>
#include <string> 
#include <map>
#include <queue>
#include <deque>
#include <vector>
#include <set>
#include <algorithm>
#include <math.h>
#include <cmath>
#include <stack>
#include <iomanip>
#define mem0(a) memset(a,0,sizeof(a))
#define meminf(a) memset(a,0x3f,sizeof(a))
using namespace std;
typedef long long ll;
typedef long double ld;
typedef double db;
const int maxn=100005,inf=0x3f3f3f3f;  
const ll llinf=0x3f3f3f3f3f3f3f3f;   
const ld pi=acos(-1.0L);
int in[35],out[35];
bool d[35][35];
string a[maxn],ans;
bool visit[35],p[35];

bool dfs(int now) {          //dfs找链
	visit[now]=1;
	ans=ans+(char)(now+'a');
	for (int j=0;j<26;j++) {
		if (d[now][j]) {
			if (visit[j]) return false;
			return dfs(j);
		}
	}
	return true;
}

int main() {
	int n,i,j,len;
	scanf("%d",&n);
	mem0(d);mem0(p);
	int flag=0;
	for (i=1;i<=n;i++) {
		cin >> a[i];
		int len=a[i].length();
		for (j=0;j<len-1;j++) {                       //邻接矩阵建图
			d[a[i][j]-'a'][a[i][j+1]-'a']=1;
	//		cout << a[i][j]-'a' <<' ' << a[i][j+1]-'a' << endl;
		}
		if (len==1) p[a[i][0]-'a']=1;
	}
	for (i=0;i<26;i++)
	if (d[i][i]) {
		printf("NO\n");return 0;
	}
	for (i=0;i<26;i++) {
		for (j=0;j<26;j++) {
			if (d[i][j]) in[j]++,out[i]++;
		}
	}
	for (i=0;i<26;i++) {
		if (in[i]>1||out[i]>1) {
			printf("NO\n");return 0;
		}
	}
	mem0(visit);
	ans="";
	for (i=0;i<26;i++) {
		if (out[i]!=0&&in[i]==0) {
			if (!dfs(i)) {
				printf("NO\n");return 0;
			}
		}
		if (p[i]&&in[i]==0&&out[i]==0) ans=ans+(char)(i+'a');         //特判单个字母
	}	
        for (i=0;i<;26;i++) {                               //判环
		if (out[i]!=0||in[i]!=0) {
			if (!visit[i]) {
				printf("NO\n");return 0;
			}
		}
	}
	cout << ans << endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值