codeforces 886D. Restoration of string (字符串处理+类拓扑排序)

搞了好久的题,现在看到这个题有点恶心,先让我缓缓……



传送门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;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值