「图论」2-SAT
\(k-SAT\)问题\((k\geq 3)\)是\(NP\)难题。
一个条件形如\(x_i\)为\(a\)或\(x_j\)为\(b\)
如何表示一个状态必须选?
添加条件:\(x_i\)为\(a\)或\(x_i\)为\(a\)
则表示,\(x_j\)为\(\sim b\)时,\(x_i\)一定为\(a\)。\(x_i\)为\(\sim a\)时,\(x_j\)一定为\(b\)
连边就行,一条边表示如果有出发点状态\(s\),一定有目的点状态\(t\)。
缩点,只要没有两个点在同一\(scc\)内则有解。
如何构造一个合法解?比较一个点两种状态的拓扑序,拓扑序较大的内个就是合法解。
为什么呢,拓扑序小的有可能走到拓扑序大的地方去,不能选,所以选大的。
\(tarjan\)缩点时,\(scc\)标号就是拓扑序的倒序。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=2e6+10,mod=998244353,inf=2e9;
int n,m;
vector<int> eg[N];
int dfn[N],low[N],idx;
int st[N],top;
int col[N],num;
inline void tarjan(int now)
{
dfn[now]=low[now]=++idx;
st[++top]=now;
for(int t:eg[now])
{
if(!dfn[t])
{
tarjan(t);
low[now]=min(low[now],low[t]);
}
else if(!col[t]) low[now]=min(low[now],dfn[t]);
}
if(dfn[now]==low[now])
{
col[now]=++num;
while(st[top]!=now) col[st[top--]]=num;
--top;
}
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;++i)
{
int x,b1,y,b2;
cin>>x>>b1>>y>>b2;
eg[x+(b1^1)*n].emplace_back(y+b2*n);
eg[y+(b2^1)*n].emplace_back(x+b1*n);
}
for(int i=1;i<=n*2;++i)
{
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;++i)
{
if(col[i]==col[i+n])
{
cout<<"IMPOSSIBLE\n";
return;
}
}
cout<<"POSSIBLE\n";
for(int i=1;i<=n;++i)
{
if(col[i]>col[i+n]) cout<<"1 ";
else cout<<"0 ";
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
1 2 3 4
*/
非常裸
前缀和优化建边。
直接连边,连通块内部变\(n^2\)条边了,多多子。
前缀和优化,新增节点\(pre[i][0,1]\)表示之前有没有选择过节点表示状态\(1\)
那么有限制:
一定要狠狠地限制。
当然理论上为了至少选一个,还需要
那个题不知道为什么不加也过过了。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=4e6+10,mod=998244353,inf=2e9;
int n,m,k;
vector<int> eg[N];
int a[N];
int dfn[N],low[N],idx;
int st[N],top;
int col[N],num;
inline int p0(int x){return x+n;}
inline int p1(int x){return x;}
inline int pre1(int x){return x+2*n;}
inline int pre0(int x){return x+3*n;}
inline void tarjan(int now)
{
dfn[now]=low[now]=++idx;
st[++top]=now;
for(int t:eg[now])
{
if(!dfn[t])
{
tarjan(t);
low[now]=min(low[now],low[t]);
}
else if(!col[t]) low[now]=min(low[now],dfn[t]);
}
if(dfn[now]==low[now])
{
col[now]=++num;
while(st[top]!=now) col[st[top--]]=num;
--top;
}
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;T=1;
while(T--)
{
cin>>n>>m>>k;
for(int i=1;i<=m;++i)
{
int x,y;
cin>>x>>y;
eg[p0(x)].emplace_back(p1(y));
eg[p0(y)].emplace_back(p1(x));
}
for(int i=1;i<=k;++i)
{
int w;cin>>w;
for(int j=1;j<=w;++j)
{
cin>>a[j];
eg[p1(a[j])].emplace_back(pre1(a[j]));
eg[pre0(a[j])].emplace_back(p0(a[j]));
}
for(int j=2;j<=w;++j)
{
eg[pre1(a[j-1])].emplace_back(pre1(a[j]));
eg[pre0(a[j])].emplace_back(pre0(a[j-1]));
eg[pre1(a[j-1])].emplace_back(p0(a[j]));
eg[p1(a[j])].emplace_back(pre0(a[j-1]));
if(j==w)
{
eg[pre0(a[w])].emplace_back(pre1(a[w]));
}
}
}
for(int i=1;i<=4*n;++i)
{
if(!dfn[i]) tarjan(i);
}
bool flag=0;
for(int i=1;i<=n;++i)
{
if(col[p0(i)]==col[p1(i)]||col[pre0(i)]==col[pre1(i)])
{
cout<<"NIE\n";flag=1;
break;
}
}
if(!flag) cout<<"TAK\n";
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
1 2 3 4
*/
先构造个合法解出来捏。
然后我们发现,某一个阵容的两个人不可能同时换去对面阵容。
我们对每个人根据当前的阵容,设置一个值\(c_i\),表示在对面阵容中和自己冲突的人数。
如果\(c_i==0\),太好了,随便跳槽。
如果\(c_i==1\),如果和自己冲突的那个人的\(c\)是零,它们俩可以换换。
\(c_i==0\)的内类人要乘起来。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=10000+10,mod=998244353,inf=2e9;
int n,m,k,ans;
vector<signed> eg[N];
signed dfn[N],low[N],idx;
signed st[N],top;
signed col[N],num;
signed b[N];
signed ct[N];
bool vis[5010][5010];
inline int p0(int x){return x+n;}
inline int p1(int x){return x;}
inline void tarjan(int now)
{
dfn[now]=low[now]=++idx;
st[++top]=now;
for(int t:eg[now])
{
if(!dfn[t])
{
tarjan(t);
low[now]=min(low[now],low[t]);
}
else if(!col[t]) low[now]=min(low[now],dfn[t]);
}
if(dfn[now]==low[now])
{
col[now]=++num;
while(st[top]!=now) col[st[top--]]=num;
--top;
}
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)
{
int w;cin>>w;
for(int j=1;j<=w;++j)
{
int x;cin>>x;
vis[i][x]=1;
eg[p1(i)].emplace_back(p0(x));
}
for(int j=1;j<=n;++j) if(!vis[i][j]&&j!=i)
{
eg[p0(i)].emplace_back(p1(j));
}
}
for(int i=1;i<=2*n;++i)
{
if(!dfn[i]) tarjan(i);
}
int s1=0,s2=0;
for(int i=1;i<=n;++i)
{
if(col[p0(i)]==col[p1(i)])
{
cout<<0<<'\n';
return;
}
if(col[p0(i)]<col[p1(i)]) ++s1;
else b[i]=1,++s2;
}
if(s1>0&&s2>0) ++ans;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j) if(i!=j)
{
if(b[i]==1&&b[j]==0&&vis[i][j]==0) ++ct[i];
if(b[i]==0&&b[j]==1&&vis[i][j]==1) ++ct[i];
}
}
int t1=0,t2=0;
for(int i=1;i<=n;++i)
{
if(ct[i]==0)
{
if(b[i]==0)
{
++t1;
if(s1>1) ++ans;
}
else
{
++t2;
if(s2>1) ++ans;
}
}
for(int j=1;j<=n;++j) if(i!=j)
{
if(ct[i]==1&&ct[j]==0&&b[i]!=b[j]) ++ans;
}
}
ans+=t1*t2;
cout<<ans<<'\n';
}
}
signed main()
{
red::main();
return 0;
}
/*
1
1 2 3 4
*/
真是好题啊
先预处理出每个炮台横着,竖着会不会打到其他炮台。
然后考虑空地,一个空地如果会被炮台横着穿过,那么只有一个炮台满足要求。因为光路可逆,如果有两个炮台能横着打到同一块空地,他俩就互相biubiu了。
竖着同理,所以一个空地最多被两个炮台穿过。
然后就是显然的\(2-SAT\)了。
再加上亿点点调试,就过了。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=10000+10,mod=998244353,inf=2e9;
int n,m;
bool flag;
char s[55][55];
vector<int> eg[N];
typedef pair<int,int> pr;
vector<int> sw[55][55];
bool vis[55][55][4];
pr q[N];int tot;
int dfn[N],low[N],st[N],col[N],idx,num,top;
inline void tarjan(int now)
{
dfn[now]=low[now]=++idx;
st[++top]=now;
for(int t:eg[now])
{
if(!dfn[t])
{
tarjan(t);
low[now]=min(low[now],low[t]);
}
else if(!col[t]) low[now]=min(low[now],dfn[t]);
}
if(dfn[now]==low[now])
{
col[now]=++num;
while(st[top]!=now) col[st[top--]]=num;
--top;
}
}
inline int id(int x,int y,int z)
{
return (x-1)*m+y+z*n*m;
}
inline int bit(int x)
{
if(x>n*m) return x-n*m;
return x+n*m;
}
inline bool dfs(int x,int y,int t,int st)
{
//cout<<x<<' '<<y<<' '<<t<<"!!"<<endl;
if(x<1||x>n||y<1||y>m) return 1;
if(s[x][y]=='#') return 1;
if(vis[x][y][t]) return 1;
if((s[x][y]=='-'||s[x][y]=='|'))
{
return 0;
}
//if(st==id(4,3,0)) cout<<x<<' '<<y<<"!!!!!!!!!!!!!!"<<endl;
vis[x][y][t]=1;
if(s[x][y]=='/')
{
if(t==0) return dfs(x,y+1,1,st);
if(t==1) return dfs(x-1,y,0,st);
if(t==2) return dfs(x,y-1,3,st);
if(t==3) return dfs(x+1,y,2,st);
}
if(s[x][y]=='\\')
{
if(t==0) return dfs(x,y-1,3,st);
if(t==1) return dfs(x+1,y,2,st);
if(t==2) return dfs(x,y+1,1,st);
if(t==3) return dfs(x-1,y,0,st);
}
bool tmp=0;
if(t==0) tmp=dfs(x-1,y,0,st);
if(t==1) tmp=dfs(x,y+1,1,st);
if(t==2) tmp=dfs(x+1,y,2,st);
if(t==3) tmp=dfs(x,y-1,3,st);
if(!tmp) return 0;
//cout<<x<<' '<<y<<' '<<t<<' '<<st<<"!!"<<endl;
if(count(sw[x][y].begin(),sw[x][y].end(),st)==0) sw[x][y].emplace_back(st);
return 1;
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
cin>>n>>m;
idx=top=num=tot=0;flag=0;
for(int i=1;i<=n*m*2;++i)
{
dfn[i]=low[i]=col[i]=0;
eg[i].clear();
}
for(int i=1;i<=n;++i)
{
cin>>(s[i]+1);
for(int j=1;j<=m;++j)
{
sw[i][j].clear();
}
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(s[i][j]!='-'&&s[i][j]!='|') continue;
for(int k=1;k<=n;++k) for(int p=1;p<=m;++p) for(int o=0;o<=3;++o) vis[k][p][o]=0;
int t0=dfs(i,j+1,1,id(i,j,0))&dfs(i,j-1,3,id(i,j,0));
for(int k=1;k<=n;++k) for(int p=1;p<=m;++p) for(int o=0;o<=3;++o) vis[k][p][o]=0;
int t1=dfs(i-1,j,0,id(i,j,1))&dfs(i+1,j,2,id(i,j,1));
//cout<<i<<' '<<j<<' '<<t0<<' '<<t1<<"!!"<<endl;
if(t0==0&&t1==0) flag=1;
if(t0==0) q[++tot]=pr(id(i,j,1),id(i,j,1));
if(t1==0) q[++tot]=pr(id(i,j,0),id(i,j,0));
}
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(s[i][j]!='.') continue;
if(sw[i][j].size()==0) flag=1;
int t0=0,t1=0;
if(sw[i][j].size()>=1) t0=sw[i][j][0];
if(sw[i][j].size()>=2) t1=sw[i][j][1];
if(!t1) q[++tot]=pr(t0,t0);
else q[++tot]=pr(t0,t1);
}
}
for(int i=1;i<=tot;++i)
{
//cout<<q[i].first<<' '<<q[i].second<<"!!"<<endl;
eg[bit(q[i].first)].emplace_back(q[i].second);
eg[bit(q[i].second)].emplace_back(q[i].first);
}
for(int i=1;i<=n*m*2;++i)
{
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n*m;++i)
{
if(col[i]==col[i+n*m]) flag=1;
}
if(flag)
{
cout<<"IMPOSSIBLE\n";
continue;
}
cout<<"POSSIBLE\n";
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(s[i][j]=='-'||s[i][j]=='|')
{
if(col[id(i,j,0)]<col[id(i,j,1)]) cout<<'-';
else cout<<'|';
}
else cout<<s[i][j];
}
cout<<'\n';
}
}
}
}
signed main()
{
red::main();
return 0;
}
/*
1
4 4
#\.-
\..-
-\.-
####
1
3 4
#.##
#--#
####
*/
同样是妙妙题。
考虑只有\(8\)个位置是\(x\),可以优先枚举\(x\)是什么。
需要\(3\)进制枚举吗?不需要!只要枚举\(x\)是\(a\)还是\(c\)就行了,因为这样不管它是\(A,B\)还是\(C\)都能取到。
#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-8)
const int N=2e5+10,mod=998244353,inf=2e9;
int n,m,k;
char s[N],t[N];
int posx[N];
vector<int> eg[N];
int dfn[N],low[N],col[N],st[N];
int idx,num,top;
struct node
{
int x,y;
char hx,hy;
}q[N];
inline int id(int x,int y)
{
return x+y*n;
}
inline void tarjan(int now)
{
dfn[now]=low[now]=++idx;
st[++top]=now;
for(int t:eg[now])
{
if(!dfn[t])
{
tarjan(t);
low[now]=min(low[now],low[t]);
}
else if(!col[t]) low[now]=min(low[now],dfn[t]);
}
if(dfn[now]==low[now])
{
col[now]=++num;
while(st[top]!=now) col[st[top--]]=num;
--top;
}
}
inline bool check()
{
idx=top=num=0;
for(int i=1;i<=n*2;++i)
{
dfn[i]=low[i]=col[i]=0;
eg[i].clear();
}
for(int i=1;i<=k;++i)
{
int t1,t2;
if(t[q[i].x]=='a'||t[q[i].x]=='b') t1=(q[i].hx=='C');
else t1=(q[i].hx=='B');
if(t[q[i].y]=='a'||t[q[i].y]=='b') t2=(q[i].hy=='C');
else t2=(q[i].hy=='B');
if(q[i].hx-'A'+'a'==t[q[i].x]) continue;
if(q[i].hy-'A'+'a'==t[q[i].y])
{
eg[id(q[i].x,t1)].emplace_back(id(q[i].x,t1^1));
continue;
}
eg[id(q[i].x,t1)].emplace_back(id(q[i].y,t2));
eg[id(q[i].y,t2^1)].emplace_back(id(q[i].x,t1^1));
}
for(int i=1;i<=2*n;++i)
{
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;++i)
{
if(col[i]==col[i+n]) return 0;
}
for(int i=1;i<=n;++i)
{
char s1,s2;
if(t[i]=='a') s1='B',s2='C';
if(t[i]=='b') s1='A',s2='C';
if(t[i]=='c') s1='A',s2='B';
if(col[i]<col[i+n]) cout<<s1;
else cout<<s2;
}
return 1;
}
inline void main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
// 二进制枚举x 是等于c还是等于C
cin>>n>>m;
cin>>(s+1);
for(int i=1,p=0;i<=n;++i)
{
if(s[i]=='x') posx[p++]=i;
}
cin>>k;
for(int i=1;i<=k;++i)
{
cin>>q[i].x>>q[i].hx>>q[i].y>>q[i].hy;
}
int S=(1<<m);
for(int ss=0;ss<S;++ss)
{
for(int i=1;i<=n;++i) t[i]=s[i];
for(int k=0;k<m;++k)
{
if((ss>>k)&1) t[posx[k]]='c';
else t[posx[k]]='a';
}
if(check()) return;
}
cout<<"-1\n";
}
}
signed main()
{
red::main();
return 0;
}
/*
5 0
bcbbb
10
1 B 1 C
3 C 1 C
2 B 5 A
1 C 1 C
2 A 5 A
3 A 1 C
2 B 1 C
3 A 1 B
1 B 4 B
1 B 4 A
*/