【DFS+2-SAT验证】LibreOJ2305(NOI2017)[游戏]题解

题目概述

A,B,C 三种车和 n 场比赛,每场比赛有一个限制 ch 表示该比赛不能用第 ch 种车(若 ch x ,表示没有限制,最多只有8个 x )。还有 m 个限制 i,hi,j,hj ,表示如果第 i 场比赛用第 hi 种车,第 j 场比赛就必须用第 hj 种车。

解题报告

ps:考网上同步赛的时候我只想到了没有 x 的做法,太弱了Orz
如果没有 x ,由于每场比赛已经限制了一种车,所以所有比赛都只能在两种车之间做选择,这是典型的2-SAT。但有了 x ,怎么办呢?
首先我们会想到DFS枚举所有 x 必定不选的车,然后就把 x 转化为普通的比赛了,接下来用2-SAT判断是否有解即可。但这样复杂度为 O(38(n+m)) ,承受不了。
继续分析,我们会发现没必要枚举把 a,b,c 全部枚举过去: a 代表的状态是第 B,C 种车, b 代表的状态是第 A,C 种车, c 代表的状态是第 A,B 种车。所以我们只需要随意选取两个枚举就可以覆盖到所有状况!
那么复杂度降为 O(28(n+m))

示例程序

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=50000,maxm=100000;

int n,D,m,ID[maxn+5],num[maxn+5][3],ans[maxn+5];;
int E,fst,lnk[2*maxn+5],nxt[2*maxm+5],son[2*maxm+5];
int ti,dfn[2*maxn+5],low[2*maxn+5],top,stk[2*maxn+5],tot,SCC[2*maxn+5];
bool instk[2*maxn+5];
struct data {int x,IDx,y,IDy;}a[maxm+5];

bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
char readc()
{
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; else return *l++;
}
int readi(int &x)
{
    int tot=0,f=1;char ch=readc(),lst='+';
    while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=readc();}
    if (lst=='-') f=-f;
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=readc();
    x=tot*f;
    return Eoln(ch);
}
char getrch() {char ch=readc();while (('z'<ch||ch<'a')&&('Z'<ch||ch<'A')) ch=readc();return ch;}
void Add(int x,int y) {son[++E]=y;nxt[E]=lnk[x];lnk[x]=E;}
void Tarjan(int x)
{
    dfn[x]=low[x]=++ti;instk[x]=true;stk[++top]=x;
    for (int j=lnk[x];j;j=nxt[j])
        if (!dfn[son[j]]) Tarjan(son[j]),low[x]=min(low[x],low[son[j]]); else
        if (instk[son[j]]) low[x]=min(low[x],dfn[son[j]]);
    if (dfn[x]==low[x])
    {
        tot++;int y;
        do y=stk[top--],SCC[y]=tot,instk[y]=false; while(y!=x);
    }
}
bool Two_SAT()
{
    E=0;memset(lnk,0,sizeof(lnk));
    for (int i=1;i<=m;i++)
    {
        int x=a[i].x,y=a[i].y,IDx=a[i].IDx,IDy=a[i].IDy;
        if (IDx==ID[x]) continue;
        if (IDy==ID[y]) Add(num[x][IDx],num[x][IDx]^1);
        Add(num[x][IDx],num[y][IDy]);
        Add(num[y][IDy]^1,num[x][IDx]^1);
    }
    memset(dfn,0,sizeof(dfn));tot=0;
    for (int i=0;i<2*n;i++) if (!dfn[i]) Tarjan(i);
    for (int i=0;i<2*n;i+=2)
        if (SCC[i]==SCC[i^1]) return false; else
        if (SCC[i]<SCC[i^1]) ans[i/2]=i; else ans[i/2]=i^1;
    return true;
}
bool Dfs(int st=0)
{
    if (st==n) return Two_SAT();
    if (ID[st]<3) return Dfs(st+1);
    num[st][ID[st]=0]=-1;num[st][1]=st<<1;num[st][2]=st<<1^1;
    bool fl=Dfs(st+1);if (fl) return true;
    num[st][ID[st]=1]=-1;num[st][0]=st<<1;num[st][2]=st<<1^1;
    fl=Dfs(st+1);ID[st]=3;return fl;
}
int main()
{
    freopen("program.in","r",stdin);
    freopen("program.out","w",stdout);
    readi(n);readi(D);
    for (int i=0;i<n;i++) switch (getrch())
    {
        case 'a':num[i][ID[i]=0]=-1;num[i][1]=i<<1;num[i][2]=i<<1^1;break;
        case 'b':num[i][ID[i]=1]=-1;num[i][0]=i<<1;num[i][2]=i<<1^1;break;
        case 'c':num[i][ID[i]=2]=-1;num[i][0]=i<<1;num[i][1]=i<<1^1;break;
        case 'x':ID[i]=3;break;
    }
    readi(m);
    for (int i=1;i<=m;i++)
    {
        readi(a[i].x);a[i].x--;a[i].IDx=getrch()-'A';
        readi(a[i].y);a[i].y--;a[i].IDy=getrch()-'A';
    }
    if (!Dfs()) return printf("-1\n"),0;
    for (int i=0;i<n;i++)
    for (int j=0;j<3;j++)
        if (ans[i]==num[i][j]) putchar(j+'A');
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值