ninja (Spoj-SOPARADE)

题目大意

有n个位置,每个位置有一个标识,范围为1到4,限制相邻两个位置的差的绝对值至少为2。同时还给出了m个限制,每个限制选定k个位置,要求这k个位置上的标识互不相同。请你输出是否存在合法的方案使得所有位置上的标识都满足限制。
n,m<=100000

2-SAT

奇数位置和偶数位置上的标识肯定是不同的,因为相邻两个位置的差的绝对值至少为2。
当然,不止这点性质。
位置1上的标识如果为1或2,位置2的标识只能为3或4,位置3的标识只能为1或2,位置4的标识又只能为3或4。。。
事实上,如果某个奇数位置上的标识为1或2,则所有奇数位置上的标识只能为1或2,所有偶数位置的标识只能为3或4。反过来,如果某个奇数位置上的标识为3或4,则所有奇数位置上的标识只能为3或4,所有偶数位置的标识只能为1或2。由于只考虑是否存在合法方案,所以这两种情况在我们眼里是等价的,考虑其中一种即可。
那么对于一个位置,我们拆成两个点,一个表示取真(对于奇数位置表示标识取1,对于偶数位置表示标识取4),一个表示取假(对于奇数位置表示标识取3,对于偶数位置表示标识取2)
那么这就是个经典的2-SAT的问题了。
对于限制相邻两个位置的差的绝对值至少为2,相当于限制相邻两个位置至少一个取真。
那么可以通过连边(有向边)来表示限制。
例如两个位置i和j,至少一个为真,设i表示i取真的点,i+n表示i取假的点,则这样连边
i+n—>j
j+n—>i
这样i取假时,j必为真;j取假时,i必为真。
另m个限制,我们可以类似的连边,有一个trick,k的取值最大为4(显然。。。)
同时奇偶位置可以分开连边,因为奇数位置和偶数位置上的标识肯定是不同的。
然后2-SAT的问题有多种算法,比如tarjan缩环之类的(身为蒟蒻并不会)
还有一种朴素的O(nm)的做法,对于所有未确定的点,取真dfs一遍,若不符要求,再取假dfs一遍,实际应用中复杂度一般都达不到理论复杂度(比如说这题),而且该算法有一个很重要的用处:求字典序最小的解!(虽然和这题没啥关系)

代码

#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+5;
int i,j,n,t,l,m,num,cnt;
int f1[5],f2[5],k[maxn*2],g[maxn*10],next[maxn*10],q[maxn*2],ba[maxn*2];
bool bz[maxn*2];
void add(int x,int y){
    next[++num]=k[x],k[x]=num,g[num]=y;
}
void ad(int x,int y){
    add(x,y+n);add(x+n,y);add(y+n,x);add(y,x+n);
}
int re(int x){
    if (x<n) return x+n;else return x-n;
}
bool dfs(int x){
    if (bz[x]||(ba[x]==cnt)) return 1;
    q[++num]=x;
    ba[x]=cnt;int y=re(x);if (bz[y]||(ba[y]==cnt)) return 0;
    int i=k[x];
    while (i){
        if (!dfs(g[i])) return 0;
        i=next[i];
    }
    return 1;
}
bool df(int x){
    num=0;
    cnt++;
    if (dfs(x)) {
        int i;fo(i,1,num) bz[q[i]]=1;
        return 1;
    }return 0;
}
int main(){
    scanf("%d",&t);
    fo(l,1,t){
        scanf("%d%d",&n,&m);num=0;
        memset(k,0,sizeof(k));
        memset(bz,0,sizeof(bz));
        memset(ba,0,sizeof(ba));cnt=0;
        bool b=1;
        fo(i,1,n) {if (i<n) add(i+n,i+1);if (i>1) add(i+n,i-1);}
        fo(i,1,m){
            int w,t1=0,t2=0;
            scanf("%d",&w);
            fo(j,1,w) {
                int y;scanf("%d",&y);
                if (y%2) f1[++t1]=y;else f2[++t2]=y;
            }if ((t1>2)||(t2>2)) b=0;
            if (!b) continue;
            if (t1==2) ad(f1[1],f1[2]);
            if (t2==2) ad(f2[1],f2[2]);
        }
        if (!b){printf("rejected\n");continue;}
        fo(i,1,n) if (!bz[i]&&!bz[i+n]){
            b=0;
            if (df(i)) b=1;else if (df(i+n)) b=1;
            if (!b) break;
        }
        if (!b) printf("rejected\n");else printf("approved\n");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值