[NOI2017]游戏

题意

有三种数,每个位置只能填一种数,有 d[0,8] d ∈ [ 0 , 8 ] 个位置有三种选择,其他位置只有两种选择

给你一些限制,表示第 i i 个位置选了某种数,那么第j个位置就只能选规定的数

输出一组合法的选数方案,无解输出 1 − 1


题解

①考虑 d=0 d = 0 ,那么就是每个位置都只有两种选择和一堆限制

那么这就是一道 2sat 2 − s a t 的裸题了

假设限制的四元组是 (i,hi,j,hj) ( i , h i , j , h j ) ,令 u=[ u = [ i i 个位置填hi的编号 ] ] ,v=[ j j 个位置填hj的编号 ] ] ,那么连边(u,v),(v,u)

注意如果 v v 是不存在的(也就是第j个位置不能填 hj h j )那么 u u 就不能选,那么就连边(u,u)

然后如果 u u 是存在的话,这个限制就废了(可以稍微快一点?)

输出方案就是建反图然后拓扑排序然后染色

其实可以直接 for f o r 一遍就好了,设 belongi b e l o n g i 表示 i i 所属的强连通分量

按照原来的做法,如果反图belongi的拓扑序小于 belongi b e l o n g i ′ 那么这个位置就选能选的第一种数,否则就是第二种

考虑到 tarjan t a r j a n 求出来的强连通分量就是按照反图的逆序给出的

原来的方法等价一下就是如果 belongi<belongi b e l o n g i < b e l o n g i ′ 就选这个位置能选的第一种,否则就是第二种

②考虑 d>0 d > 0 怎么做,就是有些位置是 3sat? 3 − s a t ?

显然不可能是 3sat 3 − s a t ,毕竟 NPC N P C

一个简单的想法就是 3d 3 d dfs d f s 枚举这个位置填什么

而且这样也没法优化了,考虑正难则反,枚举这一位不填什么

比如:

1. 1. 假设这个位置不能选 A A 数,那么就只能选B,C数;

2. 2. 假设这个位置不能选 B B 数,那么就只能选A,C数;

3. 3. 假设这个位置不能选 C C 数,那么就只能选A,B数;

我们可以发现第 3 3 种情况已经在前两种里面讨论过了,这样我们就能做到2d

总时间复杂度 O(2d(n+m)) O ( 2 d ( n + m ) )

#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
template<class T>inline void sd(T&x){
    char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c==45)y=-1;x=c-48;
    while(c=gc(),47<c&&c<58)x=x*10+c-48;x*=y;
}
char sr[1<<21],z[20];int C=-1,Z;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
template<class T>inline void we(T x){
    if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e5+5,M=2e5;
typedef int arr[N];
struct eg{int nx,to;}e[M];
struct da{int x,y;}a[N],b[N];
int n,m,ce,dft,Cnt,Top,pos[N][3];arr p,f,nt,fi,bl,dfn,low,vis,S,Ch;
void tarjan(int u){
    dfn[u]=low[u]=++dft;vis[u]=1,S[++Top]=u;
    go(u)if(!dfn[v])tarjan(v),cmin(low[u],low[v]);
        else if(vis[v])cmin(low[u],dfn[v]);
    if(dfn[u]==low[u]){
        ++Cnt;int v;
        do{vis[v=S[Top--]]=0,bl[v]=Cnt;}while(Top&&v^u);
    }
}
inline void add(int u,int v){e[++ce]={fi[u],v},fi[u]=ce;}
#define clr(x) memset(x,0,sizeof x)
inline void sol(){
    ce=Cnt=dft=0;clr(fi),clr(dfn),clr(low);
    fp(i,1,n)nt[nt[i]=i+n]=i;
    fp(i,1,m)if(p[a[i].x]^a[i].y){
        int u=pos[a[i].x][a[i].y],v=pos[b[i].x][b[i].y];
        if(p[b[i].x]==b[i].y)add(u,nt[u]);
        else add(u,v),add(nt[v],nt[u]);
    }
    fp(i,1,2*n)if(!dfn[i])tarjan(i);
    fp(i,1,n)if(bl[i]==bl[i+n])return;
        else nt[bl[i+n]]=bl[i],nt[bl[i]]=bl[i+n];
    fp(i,1,n)putchar('A'+Ch[i+(bl[i]<bl[i+n]?0:n)]);
    exit(0);
}
void dfs(int d){
    if(d==f[0]+1)return sol();int i=f[d];
    p[i]=0,pos[i][Ch[i]=1]=i;dfs(d+1);pos[i][1]=0;
    p[i]=1,pos[i][Ch[i]=0]=i;dfs(d+1);
}char s[N];
int main(){
    #ifndef ONLINE_JUDGE
        file("s");
    #endif
    scanf("%d%*d%s",&n,s+1);
    fp(i,1,n){
        p[i]=s[i]-'a';
        if(p[i]>2)f[++f[0]]=i,pos[i][2]=i+n,Ch[i+n]=2;
        else{
            if(p[i]==0)pos[i][1]=i,pos[i][2]=i+n,Ch[i]=1,Ch[i+n]=2;
            else if(p[i]==1)pos[i][0]=i,pos[i][2]=i+n,Ch[i]=0,Ch[i+n]=2;
            else pos[i][0]=i,pos[i][1]=i+n,Ch[i]=0,Ch[i+n]=1;
        }
    }scanf("%d",&m);int k=0;char c1,c2;
    fp(i,1,m){
        scanf("%d %c%d %c",&a[i].x,&c1,&b[i].x,&c2);
        if(s[a[i].x]==c1)continue;
        a[++k]={a[i].x,c1-'A'},b[k]={b[k].x,c2-'A'};
    }m=k;dfs(1);puts("-1");
return 0;
}

如果你不想写 spj s p j 的话戳这里 (by  ( b y   loj l o j ) ) <script type="math/tex" id="MathJax-Element-64">)</script>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值