搞了好久的题,现在看到这个题有点恶心,先让我缓缓……
传送门:codeforces 886D
题目大意:
给出 n 个字符串,求一个结果串,使得这 n 个字符串都是结果串中出现频率最高的子串。如果存在则输出字典序最小的结果串,反之就输出 NO。
思路:
由于 n 个字符串都是结果串中出现频率最高的子串,其出现频率为 1,就说明结果串中不存在相同的 2 个子串,不然的话该相同的子串就是出现频率最高的子串了。
先来考虑输出 NO 的情况:
1.在同一个字符串中出现至少 2 个相同的字符。根据上面说的应该很好理解。
2.在所有字符串中存在某个字符 x 的后继字符不止一个。也就是存在分支。
3.在所有字符串中存在某个字符 x 的前驱字符不止一个。也就是存在分支。
4.在所有字符串中存在环,例如以下两个字符串:“ab”、“ba”。
其实看到这个题 n 最大 10^5,容易被唬到,我们来分析一下。从上面的分析可以知道结果串最多有 26 个字符(字符各不相同),那么 26 个字符的字符串有多少个子串呢? 长度为 1 的有 26个,长度为 2 的有 25 个 …… 长度为 26 的有 1 个。共:1+2+3+……+26 = (26+1) * 27 / 2 = 351 个。所以 n > 351 的直接输出 NO 即可。
下面再来考虑存在结果串的情况下怎么求出这个结果串呢?由于前面说过每个字符的后继字符都是确定的,我们可以从第一个字符(即入度为 0 的字符)开始得到几个字符形成的字符链。这些字符链按照字典序升序排列组成的字符串就是结果。
至于判断输出 NO 的时候怎样求出度和入度,这里可以先按照字符关系建图再求出度和入度,一边输入一边求容易把相同的边重复计算。至于判断是否有环,可以在求完后字符后把每个字符的后继走一遍,如果有字符走过多次则存在环。
P个S: 我个人觉得直接对字符链升序排列然后输出是不对的……单个升序能保证组成的字符串也是升序吗?没证明过……当时先按照以下排序方式交了一发,当然是正确的了,但是比直接升序排列的时间要高一点点。
int cmp(string a,string b)
{
return a+b<b+a;
}
PPS:想明白为什么直接升序排列就可以了,因为所有的字符只出现一次,所以直接按升序排列只适用于本题。
代码:
这次的代码有点乱,因为是自己在不断的解决每一步的过程中不断修改的……
#include<iostream>
#include<algorithm>
#include<string>
#include<string.h>
using namespace std;
string s,ans[28];
int main()
{
int i,j,n,f,x,len;
int vis[28],chu[28],ru[28],mp[28][28];
while(cin>>n)
{
f=1;
memset(mp,0,sizeof(mp)); //字符构成的图
memset(ru,-1,sizeof(ru)); //每个字符的入度
memset(chu,-1,sizeof(chu)); //每个字符的出度
//在下面这个循环中,chu/ru=0则说明字符出现过,chu/ru=-1则没出现过
for(i=0;i<n;i++)
{
cin>>s; //输入字符串
memset(vis,0,sizeof(vis)); //用于判断每个字符串中字符出现的次数
len=s.length();
vis[s[0]-'a']++; //首个字符出现次数+1
ru[s[0]-'a']=0; //首个字符出现过
chu[s[0]-'a']=0; //首个字符出现过
for(j=1;j<len;j++)
{ //从第 1 个字符开始建图
int u=s[j-1]-'a'; //前一字符
int v=s[j]-'a'; //当前字符
mp[u][v]=1;
vis[v]++;
if(vis[v]>1) f=0; //如果出现次数>1,则输出 NO
ru[v]=0;
chu[v]=0;
}
}
int next[28]; //每个字符的后继字符
memset(next,-1,sizeof(next));
for(i=0;i<26;i++)
for(j=0;j<26;j++)
if(mp[i][j])
{ //如果存在边
ru[j]++; //入度+1
chu[i]++; //出度+1
next[i]=j; //记录后继
}
for(i=0;i<26;i++)
if(ru[i]>1||chu[i]>1)
{ //如果有字符的出度或入度 >1则输出 NO
f=0;
break;
}
for(i=0;i<26;i++)
{ //判断是否有环
x=i;
memset(vis,0,sizeof(vis));
while(x!=next[x])
{
if(vis[x]==1)
{ //如果当前元素访问过则有环
f=0;
break;
}
vis[x]=1;
x=next[x];
}
}
if(n>351 || !f)
{
cout<<"NO"<<endl;
continue;
}
int tol=0;
for(i=0;i<26;i++) //遍历 26个字符
if(ru[i]==0)
{ //如果入度为 0,则会形成一串字符
ans[tol]='a'+i;
x=i;
while(next[x]!=-1)
{ //加入后继字符
char c=next[x]+'a';
ans[tol]+=c;
x=next[x];
}
tol++;
}
sort(ans,ans+tol); //排序
for(i=0;i<tol;i++) cout<<ans[i];
cout<<endl;
}
return 0;
}