题目链接:Forming the Council
题目大意
一共有m个选民进行投票,有n个人可以被偷 整数表示他可以继续留在议会,负数希望他离开议会。这m个人一人投两票,问你能不能选出来几个人组成新的议会,使得所有人都满意,每个人满意的条件是自己的两票中至少有一票得到实现。
思路
典型的
2
−
s
a
t
2-sat
2−sat 问题,我们用 i 表示第一个人别选, i+n 表示他不被选。这个算法很简单。至于为什么拓扑排序的时候是逆序排的是因为
加入说现在算
a
∨
b
a\vee b
a∨b
a
∨
b
⟹
¬
a
→
b
∧
¬
b
→
a
a\vee b\Longrightarrow \neg a\to b \wedge \neg b\to a
a∨b⟹¬a→b∧¬b→a
然后我们tarjon缩完点后如果直接进行拓扑正向建图 先遇到设为正值,否则为假,那么就是
¬
a
=
=
1
→
a
=
=
0
\neg a==1 \to a==0
¬a==1→a==0 因此这个方案是错误的如果反过来建图则刚刚好。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mx=70005;
int n;
int cnt,sum,ans;
int dfn[mx],low[mx];
int color[mx];
int in[mx];
int print[mx];
int pos[mx];
int vis[mx];
int f[mx];
queue<int> q;
stack<int> st;
vector<int> vec[mx];
vector<int> ve[mx];
void init(){
for(int i=1;i<=n*2;i++){
ve[i].clear();
dfn[i]=low[i]=0;
color[i]=0;
f[i]=0;
}
for(int i=1;i<=sum;i++){
vec[i].clear();
pos[i]=in[i]=vis[i]=0;
}
while(!st.empty()){
st.pop();
}
ans=cnt=sum=0;
}
inline void add(int x,int y){
ve[x].push_back(y);
}
void tarjon(int x){
dfn[x]=low[x]= ++cnt;
f[x]=1; st.push(x);
for(int v:ve[x]){
if(!dfn[v]){
tarjon(v);
low[x]=min(low[x],low[v]);
}else if(f[v]){
low[x]=min(low[x],dfn[v]);
}
}
if(low[x]==dfn[x]){
sum++;
int k;
do{
k=st.top();
st.pop();
color[k]=sum;
f[k]=0;
}while(k!=x);
}
}
void tuopu(){
for(int i=1;i<=sum;i++){
if(!in[i]){
q.push(i);
}
}
while(!q.empty()){
int x=q.front();q.pop();
// 对给点进行标记 让其为正值
if(vis[x]==0){
vis[x]=1;vis[pos[x]]=2;
}
for(int v:vec[x]){
in[v]--;
if(!in[v]) q.push(v);
}
}
// 如果标记为 1 说明为正 可以采用这个意见
for(int i=1;i<=n;i++){
if(vis[color[i]]==1){
print[ans++]=i;
}
}
}
int main(){
ios::sync_with_stdio(0);
int t,m;cin>>t;
for(int ca=1;ca<=t;ca++){
cin>>m>>n;
for(int i=0;i<m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
int xx=x;int yy=y;
if(x<0)x=-x;
if(y<0)y=-y;
if(xx>0&&yy>0)add(x+n,y),add(y+n,x);
if(xx>0&&yy<0)add(x+n,y+n),add(y,x);
if(xx<0&&yy>0)add(x,y),add(y+n,x+n);
if(xx<0&&yy<0)add(x,y+n),add(y,x+n);
}
cout<<"Case "<<ca<<": ";
// 进行缩点
for(int i=1;i<=n*2;i++){
if(!dfn[i]) tarjon(i);
}
int flag=0;
for(int i=1;i<=n;i++){
int x=color[i];
int y=color[i+n];
// 如果正负值在一个环中,那么肯定为假不能构图。
if(x==y){
flag=1;break;
}
pos[x]=y;
pos[y]=x;
}
if(flag) {
cout<<"No\n";
init();
continue;
}
// 按照缩完点后的图 反向建图
for(int i=1,y,x;i<=2*n;i++){
x=color[i];
for(int v:ve[i]){
y=color[v];
if(x!=y){
vec[y].push_back(x);
in[x]++;
}
}
}
tuopu();
cout<<"Yes\n";
cout<<ans;
for(int i=0;i<ans;i++){
cout<<" "<<print[i];
}
cout<<"\n";
init();
}
return 0;
}