Codeforces Round #441 (Div. 1) C:National Property(2-SAT)

博客探讨了一道2-SAT问题,即Codeforces Round #441 Div. 1的C题。文章介绍了如何通过2-SAT解决字符串排序不降序的问题,分析了连边策略和处理相邻字符的逻辑,并指出对于特定情况需特判输出"No"。博主还分享了使用DFS和求强连通分量(SCC)的效率对比,并提醒读者在解题时尽量简化思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门


题解

题目有几个地方告诉我们这题是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;
}

这里写图片描述

正所谓无游戏不人生

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值