H-subsequence 2
题意
已经有一个长度为n的隐藏字符串仅有前m个小写字母构成(这m个字母不一定全部出现)
给出m*(m-1)/2个限制条件
每个限制条件有两行
第一行给出2个字母和一个len ,表示删除这个串中除了这2个字母的其他字母后串的长度为len
第二行给出这个长度为len的串
思路
我们可以发现由给出的限制条件,字母之间存在先后顺序的关系,也就是要想输出当前字母,要先把它前面的字母都输出完了才行,脑补一下就是拓扑的关系。
于是我们开始尝试建边构图。由于字符串长度有1e4,有26个字母,我们首先要给每个字母标号。
定义
a用0-9999之间的数来表示
b用10000-19999之间的数来表示
以此类推,我们就可以根据数的大小来计算这是哪个字母的第几个。
然后我们根据限制条件建边跑拓扑就可以了,如果串不存在,也就是构不成n个字符,就输出-1
#include<algorithm>
#include<vector>
#include<cstdio>
#include<cstring>
#include<queue>
#include<string>
#include<cmath>
#include<stdio.h>
using namespace std;
#define maxn 10005
#define maxm 500006
#define ll long long int
#define inf 0x3f3f3f3f
#define sc(x) scanf("%d",&x)
int n,m,p,len;
int f[30],d[maxn*50],sum[30];
char c1,c2;
char s[maxn];
vector<int>ans;
queue<int>q;
vector<int>ed[maxn*50];
bool flag=0;
void init(){
for(int i=0;i<=26;i++)f[i]=i*10000;
}
int change(int u){ //这个函数用来把数转化为字母
return u/10000;
}
int work(){
int tot=0;
for(int i=0;i<m;i++){ //枚举前i个字母
for(int j=0;j<sum[i];j++){ //前i个字母的个数
if(!d[j+f[i]])//如果这个字母的入度==0,可以塞进队列里面
q.push(j+f[i]);
}
}
while(!q.empty()){
int u=q.front();q.pop();
if(change(u)>=m)return -1;//出现了比m还大的字母,这是不可能的
ans.push_back(change(u));
tot++;
for(int i=0;i<ed[u].size();i++){
int v=ed[u][i];
d[v]--;
if(!d[v])q.push(v);
}
}
return tot;
}
int main(){
sc(n);sc(m);
init();
p=m*(m-1)/2;
while(p--){
scanf("%s%d",s,&len);//len是只有这两个字母的时候的长度
c1=s[0];c2=s[1]; //先输入这个限制的两个字母
if(len)scanf("%s",s); //有len=0的情况,是个坑点
int num1,num2,u,v;
num1=num2=0;//num1和byn2是记录这个字母是相同字母的第几个
if(len>0){
if(s[0]==c1){
u=num1+f[c1-'a'];
num1++;
}else{
u=num2+f[c2-'a'];
num2++;
}
for(int i=1;i<len;i++){
if(s[i]==c1){
v=num1+f[c1-'a'];
num1++;
}else{
v=num2+f[c2-'a'];
num2++;
}
ed[u].push_back(v);
u=v;
d[v]++;
}
}
//如果这次出现的次数和之前不一样,不符合条件,要特判
if(!sum[c1-'a']||sum[c1-'a']==num1)sum[c1-'a']=num1;
else flag=1;
if(!sum[c2-'a']||sum[c2-'a']==num2)sum[c2-'a']=num2;
else flag=1;
}
int res=work();
if(res!=n||flag){
printf("-1\n");
}else{
for(int i=0;i<ans.size();i++){
printf("%c",ans[i]+'a');
}
printf("\n");
}
return 0;
}