题目传送门
题解
题目有几个地方告诉我们这题是2-SAT。首先是变换次数不要求最小,而且任意方案就可以。而且变换只有两种,一个字母要么小写,要么大写。于是我们就可从2-SAT入手去切这题。
题目要求字符串的排序必须按照字典序的不降序。我们需要变换其中一些字母的大小写。如何找出一个合法方案呢?首先,我们把字符串序列的不降序等价变成考虑相邻两个。然后我们将相邻两个串逐位比较,如果相等,就不会带来限制,直接跳过,直到第一次出现不同,此时必须满足前面串的当前位严格小于后面的当前位。当前位分为两种情况:
①如果前面的字母小于后面的,不妨假设前面是a,后面的是b,那么如果前面选了a,后面的一定要选b,就连边a->b;而且如果后面选了B,前面就必须选A,所以连边B->A。
②如果前面的字母大于后面的,不妨假设前面是b,后面是a,那么如果前面选了B,那么后面就一定要选a;而且如果后面选了a,前面也必须选B,所以连a-B的双向边。而且还要保证不会选到A和b,所以还要连A->a,b->B的两条“自爆”边。
连边的情况(限制关系)就只有这么多。然后我们发现,第一次不同的位置处理完后,由于是字典序,后面的位置已无需处理,前面相同的一起变也不会有事,所以就处理好相邻的两个了。处理n-1次后跑一遍2-SAT就能求出一个合法方案了。需要注意的是,如果相邻的两个后面是前面的前缀,那么肯定输出“No”,这里要特判。
总共连的边数不超过4*n条,2-SAT的时间与m成正比,如果用dfs的话时间比较难说(可能并不是严格的O(m)),用求scc的方法就一定是线性的。用比较弱的数据测dfs是不超时的。
然而这道2-SAT水题就讲完了,考试时想到一个分层连边的方法,却神奇地没有调出来,还是多往简单处想比较好……
代码
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <vector>
#define maxn 100010
#define maxm 100010
#define MM 1000100
using namespace std;
int n, m, K, siz[maxn];
vector <int> a[maxn];
int back[maxm<<1], cur = -1;
struct List{
List *next;
int obj;
}Edg[MM], *head[maxm<<1];
bool vis[maxm<<1];
void Addedge(int a, int b){
Edg[++cur].next = head[a];
Edg[cur].obj = b;
head[a] = Edg+cur;
}
bool Dfs(int now){
if(now <= m && vis[now+m] || now > m && vis[now-m]) return false;
if(vis[now]) return true;
vis[now] = true;
back[++back[0]] = now;
for(List *p = head[now]; p; p = p->next){
int v = p->obj;
if(!Dfs(v)) return false;
}
return true;
}
bool Sat(){
for(int i = 1; i <= m; i++){
if(vis[i] || vis[i+m]) continue;
back[0] = 0;
if(!Dfs(i)){
for(int j = 1; j <= back[0]; j++) vis[back[j]] = false;
back[0] = 0;
if(!Dfs(i+m)) return false;
}
}
return true;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
scanf("%d", &siz[i]);
int x;
for(int j = 0; j < siz[i]; j++){
scanf("%d", &x);
a[i].push_back(x);
}
}
for(int i = 1; i <= (m<<1); i++) head[i] = NULL;
for(int i = 1; i < n; i++){
for(int j = 0; j < siz[i]; j++){
if(siz[i+1] <= j){
puts("No");
return 0;
}
if(a[i][j] != a[i+1][j]){
if(a[i][j] > a[i+1][j]){
Addedge(a[i][j]+m, a[i+1][j]);
Addedge(a[i+1][j], a[i][j]+m);
Addedge(a[i][j], a[i][j]+m);
Addedge(a[i+1][j]+m, a[i+1][j]);
}
else{
Addedge(a[i][j], a[i+1][j]);
Addedge(a[i+1][j]+m, a[i][j]+m);
}
break;
}
}
}
if(!Sat()){
puts("No");
return 0;
}
puts("Yes");
for(int i = 1; i <= m; i++) if(vis[i+m]) K ++;
printf("%d\n", K);
for(int i = 1; i <= m; i++) if(vis[i+m]) printf("%d ", i);
return 0;
}
正所谓无游戏不人生