Let's go home(HDU-1824)

Problem Description

小时候,乡愁是一枚小小的邮票,我在这头,母亲在那头。
                        —— 余光中

集训是辛苦的,道路是坎坷的,休息还是必须的。经过一段时间的训练,lcy决定让大家回家放松一下,但是训练还是得照常进行,lcy想出了如下回家规定,每一个队(三人一队)或者队长留下或者其余两名队员同时留下;每一对队员,如果队员A留下,则队员B必须回家休息下,或者B留下,A回家。由于今年集训队人数突破往年同期最高记录,管理难度相当大,lcy也不知道自己的决定是否可行,所以这个难题就交给你了,呵呵,好处嘛~,免费**漂流一日。

Input

第一行有两个整数,T和M,1<=T<=1000表示队伍数,1<=M<=5000表示对数。
接下来有T行,每行三个整数,表示一个队的队员编号,第一个队员就是该队队长。
然后有M行,每行两个整数,表示一对队员的编号。
每个队员只属于一个队。队员编号从0开始。

Output

可行输出yes,否则输出no,以EOF为结束。

Sample Input

1 2
0 1 2
0 1
1 2

2 4
0 1 2
3 4 5
0 3
0 4
1 3
1 4

Sample Output

yes
no

思路:每个人有两种选择,根据题意要满足两种条件:

1)队长留 或 两个队员留

2)由 M 指出的一对队员 a、b 的冲突条件

假设 a、b、c 三个组成一队,a 是队长,那么由条件 1 可知 队长 a 与队员 b、c 二者只能选一种,假设留为 1 走为 0,则:

对于任一人走:

  • a 走,导致 b、c 留:<a,0,b,1>、<a,0,c,1>,添边:(a+3*n,b)、(a+3*n,c)
  • b 走,导致 a 留,c 走:<b,0,a,1>,添边:(b+3*n,a)
  • c 走,导致 a 留,b 走:<c,0,a,1>,添边:(c+3*n,a)

对于 M 个条件:

  • a 留,导致 b 走:<a,b+3*n>
  • b 留,导致 a 走:<b,a+3*n>

根据以上关系,添加关系至 2-SAT 中判断即可

Source Program

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<bitset>
#define EnewPosstr 1e-9
#define newPosI acos(-1.0)
#define INF 0x3f3f3f3f
#define LL long long
const int MOD = 1E9+7;
const int N = 1000000+5;
const int dx[] = {-1,1,0,0,-1,-1,1,1};
const int dy[] = {0,0,-1,1,-1,1,-1,1};
using namespace std;

struct Edge{
    int to,next;
}edge[N*2];
int head[N],tot;
int n,m;
int dfn[N],low[N];
bool vis[N];//标记数组
int scc[N];//记录结点i属于哪个强连通分量
int block_cnt;//时间戳
int sig;//记录强连通分量个数
stack<int> S;
void init(){
    tot=0;
    sig=0;
    block_cnt=0;
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(scc,0,sizeof(scc));
}
void addEdge(int from,int to){
    edge[++tot].to=to;
    edge[tot].next=head[from];
    head[from]=tot;
}
void Tarjan(int x) {
    vis[x]=true;
    dfn[x]=low[x]=++block_cnt;//每找到一个新点,纪录当前节点的时间戳
    S.push(x);//当前结点入栈

    for(int i=head[x]; i!=-1; i=edge[i].next) { //遍历整个栈
        int y=edge[i].to;//当前结点的下一结点
        if(!dfn[y]) {
            Tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(vis[y])
            low[x]=min(low[x],dfn[y]);
    }

    if(dfn[x]==low[x]) { //满足强连通分量要求
        sig++;//记录强连通分量个数

        while(true) { //记录元素属于第几个强连通分量
            int temp=S.top();
            S.pop();
            vis[temp]=false;
            scc[temp]=sig;
            if(temp==x)
                break;
        }
    }
}
bool twoSAT(){
    for(int i=1;i<=6*n;i++)//总点数
        if(!dfn[i])
            Tarjan(i);
    for(int i=1;i<=3*n;i++)
        if(scc[i]==scc[i+3*n])//遍历是不是会有一个人有2种可能
            return false;
    return true;
}
int main() {
    while( scanf("%d%d",&n,&m)!=EOF&&(n+m)){
        init();
        for(int i=1;i<=n;i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            a++,b++,c++;
            addEdge(a+3*n,b);//a不留b留
            addEdge(a+3*n,c);//a不留c留
            addEdge(b+3*n,a);//b不留a留
            addEdge(c+3*n,a);//c不留a留
        }
        while(m--) {
            int a,b;
            scanf("%d%d",&a,&b);
            a++,b++;
            addEdge(a,b+3*n);//a留b不留
            addEdge(b,a+3*n);//b留a不留
        }

        bool flag=twoSAT();
        if(!flag)
            printf("no\n");
        else
            printf("yes\n");
    }
    return 0;
}

 

posted @ 2022-09-20 23:00  老程序员111  阅读(0)  评论(0编辑  收藏  举报