[做题笔记] 2-sat 问题的进阶应用
对称性
考虑 边的意义是:如果选取了 则必须选取 ,那么如果我们连边 ,我们都是也需要连边 ( 即表示变量 的逆),因为原命题和其逆否命题真假相同。
那么发现这样建出来的图具有某种对称性,此性质是 算法最重要的性质。
解的构造
设 表示 所在强连通分量的拓扑序编号(编号小的拓扑序大),那么如果 我们选取 ;否则我们选取
要证明上述构造是正确的,我们只需要证明被选取点不能到达没有未被选取的点。使用反证法,假设存在被选取点 和未被选取 ,并且存在 的路径,那么显然有 ,同时根据对称性,存在一条 的路径,所以有
根据选取的关系可以知道:,所以可以推出 ,但是这与 矛盾,反证法证毕。
特殊的边
这里有一个小 :如果 强制不能选取,那么可以连一条 的边,表示强制不能选
这样连边还是满足对称性,用处很多:NOI2017 游戏
字典序最小的解
这种问题建议用直接 的方法,也就是先搜字典序小的再搜索字典序大的。
这个问题也是可拓展的,既然是字典序问题就很容易和贪心产生联系:New Language
前后缀优化建图
本质思想还是建虚点来优化建图,连向这个虚点就代表了连向一个前缀后缀。
如果你遇到要连向除一个点之外所有点的问题,那么可以拆成前缀与后缀的连边:Duff in Mafia
Ants (树链剖分优化建图)
题目描述
解法
我们把路径当成点建 ,每只蚂蚁的两条路径互为逆元。那么如果两条路径 有交,那么可以连接 和 ,有一个建图的小技巧是把 当成无序对,然后把这两条边一次性连好。
我们先把这些路径通过树链剖分放在线段树上,那么我们现在想完成的功能是:把某点和子树中的所有其他点连边。注意这里不要被传统的线段树优化建图束缚住了,这里我们可以魔改经典的前后缀优化建图的思想:
其中黑色点代表原线段树上的节点;红色点代表这一层建出的虚点,点数和节点的路径数量一致;绿色点表示节点的路径;蓝色点表示路径的逆。上述建图方法的主体还是前后缀优化建图,只是为了连到子树内,把上一层的最后一个虚点当成这一层的第一个虚点,时间复杂度
#include <cstdio>
#include <vector>
using namespace std;
const int M = 100005;
const int N = 10000005;
#define pb push_back
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,cnt,tot,f[M];vector<int> g[N],vc[M<<2];
int Ind,num[M],siz[M],son[M],top[M],fa[M],dep[M];
int k,scc,dfn[N],low[N],col[N],s[N],in[N];
struct edge{int v,next;}e[M<<1];
void add(int u,int v) {g[u].pb(v);g[v^1].pb(u^1);}
void dfs1(int u,int p)
{
siz[u]=1;fa[u]=p;
dep[u]=dep[p]+1;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==p) continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp;num[u]=++Ind;
if(son[u]) dfs2(son[u],tp);
for(int i=f[u];i;i=e[i].next)
if(e[i].v^fa[u] && e[i].v^son[u])
dfs2(e[i].v,e[i].v);
}
void ins(int i,int l,int r,int L,int R,int w)
{
if(L>r || l>R) return ;
if(L<=l && r<=R) {vc[i].push_back(w);return ;}
int mid=(l+r)>>1;
ins(i<<1,l,mid,L,R,w);
ins(i<<1|1,mid+1,r,L,R,w);
}
void add(int u,int v,int w)
{
while(top[u]^top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
ins(1,1,n,num[top[u]],num[u],w);
u=fa[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);
if(u!=v) ins(1,1,n,num[v]+1,num[u],w);
}
void build(int i,int l,int r,int p)
{
int o=vc[i].size(),u=++cnt,v=(cnt+=o);
if(o) add(v-1<<1,v<<1);
else if(p) add(p<<1,v<<1);
for(int j=0;j<o;j++)
{
int w=vc[i][j];add(w,u+j<<1);
if(j) add(u+j-1<<1,w^1),add(u+j-1<<1,u+j<<1);
else if(p) add(p<<1,u<<1),add(p<<1,w^1);
}
if(l==r) return ;
int mid=(l+r)>>1;
build(i<<1,l,mid,v);
build(i<<1|1,mid+1,r,v);
}
void tarjan(int u)
{
low[u]=dfn[u]=++Ind;
s[++k]=u;in[u]=1;
for(int v:g[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
int v;scc++;
do
{
v=s[k--];
col[v]=scc;in[v]=0;
}while(u!=v);
}
}
signed main()
{
n=cnt=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
e[++tot]=edge{v,f[u]},f[u]=tot;
e[++tot]=edge{u,f[v]},f[v]=tot;
}
dfs1(1,0);dfs2(1,1);m=read();
for(int i=1;i<=m;i++)
{
add(read(),read(),i<<1);
add(read(),read(),i<<1|1);
}
build(1,1,n,0);Ind=0;
for(int i=1;i<=2*cnt;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=m;i++)
if(col[i<<1]==col[i<<1|1])
{puts("NO");return 0;}
puts("YES");
for(int i=1;i<=m;i++)
puts(col[i<<1]<col[i<<1|1]?"1":"2");
}
精准预测(有解结论再探)
题目描述
解法
其实这题我完全是有能力做出来的,一定要把遇到的结论总结好,用的时候才可以行云流水。
考虑每个人的每个时刻具有生存和死亡两种状态,那么我们把这个建成 ,记 表示第 个人的时刻 是生存状态, 表示第 个人的时刻 是死亡状态,那么我们这样连边:
- 难兄难弟: 连向 ,同时我们把 连向
- 死神来了: 连向 ,同时我们把 连向
- 因为生存和死亡的连续性,我们把 连向 ,把 连向
连出上面的图还是推荐使用对称性,这样就只用连一半了。
然后我们想要优化这张图的点数,发现只有出现过的 和 是需要保留的,这样点数一共只有 个,注意这里不要保留 ,因为是死亡所以可以等效地连到后面的第一个点(这点常数要卡好)
我们再来分析一下图的性质,由于单看 ,都是时间单调的图,而第二类边有只会带来 的边,所以说这是一个拓扑图,所以至少存在一组解(全都是死亡状态),并且不存在某个点都是覆盖点 和 的情况。
现在我们的问题是判断两个生存状态是否能共存,我们可以判断强制选取这两个点之后是否还有解。利用最小字典序的结论:如果当前局面不出现矛盾,那么如果原来有解现在就一定有解。
计算 我们可以先把 (状态 简记为 )选取了,那么考虑 可以到达的死亡状态的集合是 ,那么要求 ,此外还要求 不能是初始必死,这样就可以保证当前局面不出现矛盾了。
在 上要处理出一个点能到达的点集,可以用 ,由于空间限制我们每 个简单做一次,这样 的大小就只用开成 了,时间复杂度
#include <cstdio>
#include <vector>
#include <bitset>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 300005;
const int N = 1005;
#define pb push_back
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
void write(int x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int k,n,m,ty[M],t[M],x[M],y[M];
int vis[M],s[M],f[M],ans[M],die[M],in[M];
bitset<N> dp[M];vector<int> v[M],g[M];
int id(int i,int j) {return s[i-1]+j+1;}
//x<<1 alive ; x<<1|1 dead
void add(int x,int y) {g[x].pb(y);g[y^1].pb(x^1);}
//something that I mistake x^1 with x .....
void build()
{
for(int i=1;i<=n;i++) v[i].pb(k+1);
for(int i=1;i<=n;i++) s[i]=s[i-1]+v[i].size();
for(int i=1;i<=n;i++)
{
sort(v[i].begin(),v[i].end());
int len=v[i].size();
for(int j=0;j+1<len;j++)
add(id(i,j+1)<<1,id(i,j)<<1);
}
for(int i=1;i<=m;i++)
{
int p1=lower_bound(v[x[i]].begin()
,v[x[i]].end(),t[i])-v[x[i]].begin();
int p2=lower_bound(v[y[i]].begin()
,v[y[i]].end(),t[i]+(!ty[i]))-v[y[i]].begin();
if(!ty[i])
add(id(x[i],p1)<<1|1,id(y[i],p2)<<1|1);
else
add(id(x[i],p1)<<1,id(y[i],p2)<<1|1);
}
for(int i=1;i<=n;i++)
f[i]=id(i,v[i].size()-1)<<1;
}
void dfs(int u)
{
if(vis[u]) return ;vis[u]=1;
if(!in[u]) dp[u].reset();
for(int v:g[u])
dfs(v),dp[u]=dp[u]|dp[v];
}
void work()
{
for(int l=1,r;l<=n;l+=N)
{
r=min(l+N-1,n);
memset(in,0,sizeof in);
memset(vis,0,sizeof vis);
for(int i=l;i<=r;i++)
in[f[i]|1]=1,dp[f[i]|1].set(i-l);
for(int i=1;i<=n;i++) dfs(f[i]);
bitset<N> ban;
for(int i=l;i<=r;i++) if(dp[f[i]][i-l])
die[i]=1,ban[i-l]=1;//then i must die
for(int i=1;i<=n;i++)
ans[i]+=r-l+1-(ban|dp[f[i]]).count();
}
for(int i=1;i<=n;i++)
write(die[i]?0:ans[i]-1),putchar(' ');
}
signed main()
{
k=read();n=read();m=read();
for(int i=1;i<=m;i++)
{
ty[i]=read();t[i]=read();x[i]=read();y[i]=read();
v[x[i]].pb(t[i]);//the crucial points
}
build();work();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2021-02-19 [正睿集训2021] 模拟赛1