题目
分析
先把单词按给定的顺序排好。
然后依次比相邻的两个单词,只要不是包含关系,一定能且只能得到一对字母的大小关系,把大的字母向小的字母连一条边。
例如:abc
和add
,要求替换后abc
<add
。
第一位a
是一样的,不论怎么替换都没用,跳过。
第二位一个是b
一个是d
,由于要求替换后abc
<add
,所以必须保证替换后的b
<d
。
这时把d
向b
连一条边。
这样操作后能得到一个图。
显然如果图中有环,输出NE
。
否则,我们就会得到一个图,表示的是替换后要满足的字母大小关系。
这个图虽然不是树(因为可能有下面这种情况),但是有一个根(无入度的点)。
其实边E
是多余的,如果能把这样的边都删掉就简单了,但是要删掉这种多余的边很麻烦。
所以我在考试的时候使用了一种玄学方法,创新驱动发展 找到“根”(如图中的b
),用SPFA跑一遍从根开始的最长路(还可以顺便判环),得到根与每个点
i
i
i的最长距离
d
i
d_i
di,将
d
d
d从大到小排序后,依次给
d
i
d_i
di路径上的点编号,这样就能避免上图中E
这样的边了。
就这样,我得了0分。
最后发现是DA
打成了DE
,又是绑点数据,我丢,,,,,,
然而改正后还是只有80分,发现忽略了一个细节问题,当建出来的图没有边(即每个单词都是后一个的前缀)时,是有解的,我的代码太丑了,没考虑到这种问题。
所以虽然下面给的代码是AC的,但是大家最好别看。
PS.这道题本来是想考拓扑排序,,,,,,
代码
真的不建议看!!!
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
#define MAXN 100
#define MAXL 100
int N;
struct node{
int len;
char str[MAXL+5];
}A[MAXN+5];
int P[MAXN+5];
char S[MAXN+5][MAXL+5];
int F[30];
vector<int> G[30];
char Ans[30];
bool vis[30];
int dist[30],pre[30],cnt[30];
bool SPFA(int S,int n){
queue<int> Q;
Q.push(S);
memset(dist,0x3f,sizeof dist);
dist[S]=0;
pre[S]=-1;
vis[S]=cnt[S]=1;
while(!Q.empty()){
int u=Q.front();Q.pop();vis[u]=0;
for(int i=0;i<int(G[u].size());i++){
int v=G[u][i];
if(dist[u]-1<dist[v]){//每条边取负求最短路 得到最长路
dist[v]=dist[u]-1;
pre[v]=u;
if(!vis[v]){
vis[v]=1;
cnt[v]++;
if(cnt[v]>n)//判环(负权回路)
return 0;
Q.push(v);
}
}
}
}
return 1;
}
struct path{
int len,To;
}T[30];
bool cmp(path x,path y){
return x.len>y.len;
}
char C='z';
void Update(int u){
if(Ans[u])
return;
Update(pre[u]);
Ans[u]=C--;
}
int main(){
freopen("cezar.in" ,"r", stdin);
freopen("cezar.out","w",stdout);
N=read();
for(int i=1;i<=N;i++)
scanf("%s",S[i]+1);
for(int i=1;i<=N;i++){
P[i]=read();
A[i].len=strlen(S[P[i]]+1);
memcpy(A[i].str,S[P[i]],sizeof S[P[i]]);
}//按给定顺序排
bool flag=1;
for(int i=2;i<=N;i++){
int j=1;
while(A[i-1].str[j]==A[i].str[j])
j++;//找到第一个不同的字母
if(j==A[i-1].len+1)
continue;//前缀
flag=0;
if(j==A[i].len+1){
puts("NE");
return 0;
}
G[A[i].str[j]-'a'+1].push_back(A[i-1].str[j]-'a'+1);
F[A[i-1].str[j]-'a'+1]=A[i].str[j]-'a'+1;
}//建图,建议字母编号从1开始,不然后面有点麻烦
if(flag){
puts("DA");
for(int i=0;i<26;i++)
putchar('a'+i);
return 0;
}//这玩意就是处理每个都是后一个的前缀的问题
int Root=0,cnt=0;
for(int i=1;i<=26;i++){
if(G[i].size()&&!F[i])
Root=i;
cnt+=F[i];
}//找根
if(!Root){
puts("NE");
return 0;
}
for(int i=1;i<=26;i++)
if(i!=Root&&!F[i])
G[Root].push_back(i);
if(!SPFA(Root,cnt+1)){
puts("NE");
return 0;
}
Ans[Root]=C--;
for(int i=1;i<=26;i++){
T[i].To=i;
T[i].len=dist[i];
}
sort(T+1,T+26+1,cmp);
for(int i=1;i<=26;i++){
int tmp=T[i].To;
Update(tmp);//递归找路径编号
}
puts("DA"),puts(Ans+1);
}
PS.据说用拓扑排序60行。