题解:CF875C National Property

大致题意:给定一些字符串,字符串中的字母开始时均为小写,你可以将其中若干字母全部改为大写,问能否使得操作后给定的字符串满足按字典序升序(可以等于)排列。如果能,则输出方案。

这里提供一个拓扑排序的解法。

首先,由于影响两个字符串的字典序大小关系的只有两个字符串的第一个不同的字母,所以我们只需要让所有两个相邻字符串的第一个不同的字母升序排列。

由于一个字母可能要满足与其它多个字母的大小关系,所以我们可以考虑建图以处理字母之间的大小关系,并开一个 \(b\) 数组记录字母的大小写状态, \(b_i\)\(0\) 为不确定是否要改,为 \(1\) 为必须为大写,为 \(2\) 为必须为小写。对于字母 \(x,y\),若操作后字母 \(x\) 必须小于字母 \(y\),则建边 \(x \to y\)

建图后不难发现若该图有环或某个字母的大小写状态存在矛盾则无解。除了有环的情况,对于一条边 \(x \to y\) 上的点 \(x,y\),当存在以下情况时无解:

  • 若当前字母 \(x\) 的值大于某个必须大于当前字母的字母 \(y\) 的值且该字母 \(y\) 已定为大写,即 \(b_y = 1\)\(x > y\)

  • 若当前字母 \(x\) 的值大于某个必须大于当前字母的字母 \(y\) 的值且当前字母 \(x\) 已定为小写,即 \(b_x = 2\)\(x > y\)

  • 若某个必须大于当前字母的字母 \(y\) 已定为大写且当前字母 \(x\) 已定为小写,即 \(b_x = 2\)\(b_y = 1\)

在拓扑排序时,按照拓扑排序的顺序依次遍历所有节点,若当前节点状态为不确定是否要改,则定为大写以方便后续字母的操作。定下当前节点的状态后依次遍历当前节点指向的节点,并根据当前节点的状态定下当前节点指向的节点的状态,并判断是否矛盾,在拓扑排序结束后判断是否有环即可。

最后不要忘记判断若当前字符串是上一个字符串的前缀且当前字符串的长度小于上一个字符串的长度,则当前字符串必定小于上一个字符串,无解。

代码:

#include<cstdio>
#include<queue>
#include<vector>
#define MAXN 100005
#define int long long
using namespace std;
//b数组:0为不确定是否要改 1为必须为大写 2为必须为小写 
int n,m,head[MAXN],d[MAXN],cnt,tmp,ans[MAXN],sum,b[MAXN];
vector<int>v[MAXN];
struct Node{
	int value,next;
}adj[MAXN];
void insert(int a,int b){
	tmp++;
	adj[tmp].value=b;
	adj[tmp].next=head[a];
	head[a]=tmp;
}
bool topsort(){ //返回0为无解 返回1为有解 
	cnt=0; 
	queue<int>q;
	for(int i=1;i<=m;i++){
		if(d[i]==0){
			q.push(i);
		}
	}
	while(q.size()){
		int t=q.front();
		cnt++;
		if(b[t]==0){ //若当前字母不确定是否要改 
			b[t]=1;  //则将状态改为大写以方便后面的字母操作 
			sum++;   
			ans[sum]=t;
			for(int i=head[t];i!=0;i=adj[i].next){
				int j=adj[i].value;
				if(j<t){ //若当前字母的值大于某个必须大于当前字母的字母的值 
					if(b[j]==1){ //且该字母已定为大写 
						return 0; //则无解 
					}
					b[j]=2; //该字母必须小写 
				}
			}
		}else{
			for(int i=head[t];i!=0;i=adj[i].next){
				int j=adj[i].value;
				if(j<t){ //若当前字母的值大于某个必须大于当前字母的字母的值
					return 0; //由于当前字母必须为小写,所以当前字母无法小于该字母,无解 
				}else{           //否则 
					if(b[j]==1){ //若某个必须大于当前字母的字母已定为大写 
						return 0;//则当前字母无法小于该字母,无解
					}
					b[j]=2; //该字母必须小写
				}
			}
		}
		q.pop();
		for(int i=head[t];i!=0;i=adj[i].next){
			d[adj[i].value]--;
			if(d[adj[i].value]==0){
				q.push(adj[i].value);
			}
		}
	}
	if(cnt<m)return 0; //若有环,则无解 
	else return 1;
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++){
		int l;
		scanf("%lld",&l);
		for(int j=1;j<=l;j++){
			int x; 
			scanf("%lld",&x);
			v[i].push_back(x); 
		}
		if(i>1){ 
			int flag=0,lastl=v[i-1].size(),minn=min(lastl,l);
			for(int j=0;j<minn;j++){
				if(v[i-1][j]!=v[i][j]){ //将两个字符串的第一个不同的字母建边 
					flag=1;
					insert(v[i-1][j],v[i][j]);
					d[v[i][j]]++;
					break;
				}
			}
			if(!flag&&lastl>l){ //若当前字符串是上一个字符串的前缀且当前字符串的长度小于上一个字符串的长度 
				printf("No");   //则当前字符串必定小于上一个字符串,无解 
				return 0;
			}
		}
	}
	if(!topsort()){
		printf("No");
		return 0;
	}
	printf("Yes\n%lld\n",sum);
	for(int i=1;i<=sum;i++){
		printf("%lld ",ans[i]);
	}
	return 0;
} 

感谢管理员辛苦审核。

posted @ 2025-11-02 22:16  CharlieCai2024  阅读(17)  评论(0)    收藏  举报