【图论与网络流】
网络流建模思路
想办法让一个流或一组割代表一种方案。
无源汇上下界可行流
先令每条边的流量为它的下界,此时每个点可能不满足流量守恒。对每个点求出它的净流量:即流入减流出。
有源汇上下界可行流
集合划分模型
假设有若干个元素,每个元素要么放到A,要么放到B。每个元素放到A/B有相应的代价,还有形如“若x在集合A,y在集合B,则有z的代价”的限制。
Sol:
一个元素如果最终和S相连,则相当于分到了A集合,如果和T相连则分到B集合。
连边,跑最小割即可。
注意,如果边权存在负数,可以令该元素连向A的边和连向B的边同时加一个正数,在最终答案中减去加的这个数即可。
这样做的正确性在于该元素连向A的边和连向B的边一定割且仅割一条边,那么不管割哪条边,对最终的影响是相同的。
最大权闭合子图
给定一张有向图,点有点权,选一个点集使得这个点集中的任意一条出边仍然指向这个点集内部。要求点权和的最大值。
实际上,最大权闭合子图有一个更加贴合实际建模的定义:有若干个限制,每个限制形如选了\(u\)就必须选\(v\),选\(u\)有\(a_u\)的价值,求最大价值。
考虑集合划分模型。不选的话有\(w_u\)的代价,那么从\(S\)向\(u\)连\(w_u\)的边,从\(u\)向\(T\)连\(0\)边,对于原图中的一条边\((u,v)\),同样连一条\((u,v)\)的边,边权为\(inf\)。
但是\(w_u\)肯定有负数,否则直接选全集就行了,但是网络流的容量是不能有负数的。我们令边权小于\(0\)的点连向\(S\)的边和连向\(T\)的边同时加上\(-w_u\),那么边权就都为正了。然后边权为\(0\)的边我们实际上就可以直接忽略了。
最终答案为\(\sum_{w_u>0}w_u-mincut\)
最小路径点覆盖
Pro:给定一张DAG,要求用最少路径(不重复)覆盖所有点
Sol:
考虑调整的方法,初始每个点作为一条单独的链,显然这是合法的。考虑合并链。
把每个点拆成出点和入点,对于原图中的边,在拆点二分图上从出点到入点连边。那么
解释:
每条匹配边相当于把两条链连接起来,也就是所选的路径上的边。每选一条,答案减一。每个出点只能选一条匹配边,每个入点也只能选一条匹配边,这与路径上的点最多有一个前驱和后继相对应。
注意,只有DAG能这么做,如果有环,二分图也可能出现环。
ex
如果允许路径相交,相当于可以跳过路径上的一些点。这时,我们先做一遍传递闭包,便可实现“跳过去”这一操作,再做普通的最小路径点覆盖即可。
费用流
SSP算法可以求解初始无负环情况下的费用流。复杂度O(nmf),但是出题人一般不会卡这种算法。
可以证明,只要初始无负环,那么在增广过程中,残量网络上一定不会出现负环。OIwiki上的证明
但是有可能会出现零环,因此dinic函数中要加一个vis标记,不重复访问节点。
二分图最小点覆盖
对于一般图显然有最小点覆盖大于等于最大匹配,这是因为每个匹配边都至少需要一个点来覆盖
而根据konig定理可以证明二分图最小点覆盖等于最大匹配
证明方式考虑从左部的每个非匹配点出发重新找一次增广路(一定会失败),把经过的点都进行标记,最终左部所有的未标记点和右部所有的标记点即构成最小点覆盖
实际求的过程中用最小割可以很方便的实现(分别枚举S和T所连的剩余容量为0的边对应的点即为最小点覆盖)
二分图最大独立集
容易发现最大独立集和最小点覆盖是一一对应的互补关系,两者互为补集,直接取最小点覆盖剩下的点就是最大独立集。
差分约束
对若干个变量之间的差分进行约束,求出一组解(有环则无解)
注意,在求最短路时,差分约束会直接求出字典序最大的解,证明考虑最短路树(每个变量会尽量顶满上界)
如果要求字典序最小,只需令所有变量取反,然后相应的修改限制即可。例如\(x_i<=x_j+dis=>(-x_j)<=(-x_i)+dis\),也就是把连边的方向取反,最后再反回来即可。
[AGC056C]
很厉害的题。
如果设vi为每个点的前缀和进行差分约束的话,需要用spfa来求最短路,考虑给\(v_i\)换一个意义,设\(v_i\)表示前缀\(i\)中0的数量-1的数量。那么区间的限制只需在两端点处连0的边权即可。
对于相邻两点间的关系我们先连长度为1的边。题目让字典序最小也就是\(v_i\)字典序最大,直接差分约束即可。
那么是否会出现相邻点的\(v\)相等的情况,感性理解一下在要求字典序最大的情况下,你让两个相等一定不优。
带权并查集
似乎可以做csp2022t4?复杂度很优秀?
dfs树
CF118E
P4151
tarjan
CF160D
给定一张无向图,输出哪些边可以在最小生成树中,哪些边一定在最小生成树中,哪些边一定不在最小生成树中。
先求出一颗最小生成树,考虑每条边能否被替换?
圆方树
对于一个无向图,给他的每个点双(一条边也视为一个点双)都定义一个方点。其他点都是圆点
方点向它对应的点双中的每个圆点连边,形成一颗树,即为圆方树。
性质:圆点只和方点连边,方点只和圆点连边
构建:类似于求割点,每次要从栈中弹出一个点双时,建一个方点与这些点连边
说到简单路径,就必须提一个关于点双很好的性质:对于一个点双中的两点,它们之间简单路径的并集,恰好完全等于这个点双。
即同一个点双中的两不同点 u,v 之间一定存在一条简单路径经过给定的在同一个点双内的另一点 w。
证明考虑

只有红黑有交、红紫有交、绿黑有交、绿紫有交的情况可能找不到x-y-z的路径
然后以绿黑有交为例,必然是走完黑之后,绿的一段前缀是黑的一段后缀,否则一定可以调整成这样
又因为绿紫有交,所以绿的一段前缀是紫的一段后缀
所以紫、黑就出现公共后缀了,但是紫黑是不交的
于是就出现了矛盾。也就是说如果绿黑有交,令绿和黑拥有一段共同后缀,那么绿和紫一定没有交,所以结论成立。
由此可以推出:原图中两个点的所有简单路径的并即为两点在圆方树路径上所有方点所代表的点双的并。
铁人两项代码,注意其中圆方树的构建部分,
namespace Tj
{
int dfn[N],low[N],tot,stk[N],top,num,siz[N],w[N],s[N],fa[N];
vector<int>v[N];
int ans;
void tarjan(int x)
{
dfn[x]=low[x]=++tot;
stk[++top]=x;
for(int i=hd[x];i;i=e[i].nxt)
{
int y=e[i].to;
if(dfn[y]) low[x]=min(low[x],dfn[y]);
else
{
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
++num;int z;
do
{
z=stk[top--];
v[z].pb(num);v[num].pb(z);
w[num]++;
}while(z!=y);
w[num]++;v[x].pb(num);v[num].pb(x);
}
}
}
}
void dfs(int x,int sum,int num)
{
sum+=w[x];
if(x<=n) ans+=(num-1)*sum,siz[x]=1;
int tmp=0;
for(int i=0;i<v[x].size();++i)
{
int y=v[x][i];
if(y==fa[x]) continue;
fa[y]=x;dfs(y,sum,num);siz[x]+=siz[y];
s[x]+=siz[y]*(siz[y]-1)/2;tmp+=s[y];
}
s[x]=siz[x]*(siz[x]-1)/2-s[x];
ans-=sum*s[x];
ans-=tmp*sum;
}
void work()
{
num=n;for(int i=1;i<=n;++i) w[i]=-1;
for(int i=1;i<=n;++i)
if(!dfn[i])
{
int tt=tot;
tarjan(i);
top--;
dfs(i,0,tot-tt);
}
cout<<ans*2;
}
}
2-SAT
问题:
给定n个bool 变量,和若干个限制,每个限制都只牵涉两个变量,即为2-SAT,若一个限制同时牵涉多个变量,即为k-SAT,是NPC问题。
Sol:
类似扩展域并查集,把每个变量拆成\(x\)和\(x'\),把限制转化成\(x\Rightarrow y\)的形式,并形象地从\(x\)到\(y\)连一条有向边。注意逆否命题也要连边,因此如果忽略边的方向,2-SAT的连边是对称的。
将所有限制转化完后,对得到的有向图进行强联通缩点,注意到一个强连通分量中的命题要么全都为真,要么全都为假。
定理:当且仅当存在一个变量\(x\)和\(x'\)在同一个强连通分量中时,2-SAT问题无解
首先,充分性是显然的,\(x\)和\(x'\)不可能同时为真或假
否则,一定能构造出一组解。
构造解:
缩点后的图形成一个DAG,由于2-SAT问题连边的对称性,如果\(x\)和\(y\)在同一个SCC中,那么\(\lnot x\)和\(\lnot y\)也必定在同一个强连通分量中,因此缩点之后的DAG上的每个点也都有唯一对应的一个点,拥有恰好大小相等,所含的变量完全相反的点。
具体的构造方法如下:
按拓扑序从大到小,检查这个点是否被标记过,若没有,则令这个点为真,同时标记这个点对应的点为假。
若已被标记过为假,则检查下一个点。
证明:
首先,如果这个点没有被标记过,那么令他为真,它的前驱不论真假都不会导致矛盾。因此成立。
如果这个点被标记过为假,我们需要证明这个点不存在一个前驱为真。
采用反证法:设这个点\(x\)为假,这个点的一个前驱\(y\)为真,可以得到\(y\Rightarrow x\),且\(y\)的拓扑序小于\(x\)。又因为\(x\)被标记成假,所以\(\lnot x\)的拓扑序大于\(x\)。因为\(y\)为真,说明\(\lnot y\)的拓扑序小于\(y\)。根据\(y\Rightarrow x\)可以得到\(\lnot x\Rightarrow \lnot y\),也就是\(\lnot x\)的拓扑序小于\(\lnot y\),这就产生了矛盾。
优化
实际上经过tarjan缩点后,SCC标号越小的,DAG上的拓扑序越大,利用这个性质有很简单的代码:
for(int i=1;i<=n;++i)
val[i]=c[i]<c[i+n];
构造字典序最小的解
直接按位贪心即可。每次检查这一位能否为真,遍历所有能到达的点,检查是否会产生矛盾。若没有产生矛盾,把遍历到的点都标记成真,这些点的对立点标记成假。需要证明局部贪心策略不会影响全局合法性。(菇)

浙公网安备 33010602011771号