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 22 4
0 1 2
3 4 5
0 3
0 4
1 3
1 4Sample 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;
}