拟阵学习笔记
0.前言
dmy 讲课时讲了一个看起来很牛马牛逼的东西,但是他讲的太拉了我太菜了,所以去查了些资料才搞懂一点
然鹅……
所以我这个蓝名学这个干什么
由于我比较菜,可能写的会有一些错误的地方,望各位大佬指正
1. 拟阵概念
拟阵是一个很强大的模型,可以解决一大坨组合最优问题,学会了它你可以吊打 tourist
那么什么是拟阵呢?
假设我们现在有一个有限集
为简化这个 DAG,我们忽略那些集合大小差大于
比如有限集
以后我们称两个集合一个比另一个大当且仅当它们的集合大小一个比另一个大
现在我们想选出其中几个子集,用它们组成一个新的集合
- (遗传性)每个在
S 中的集合,它的所有子集都在S 中(\forall A \in S,B \subset A,B \in A )
也就是在上面那张由
- (交换性)只要
A 包含的元素个数不是在S 里的集合最大的,必定可以在S 中找到一个恰好比A 多一个元素的集合B (\forall A,B \in S \land |A|<|B|,\exists a \in M,a \notin A,A \cup {a} \in S )
也就是将上面的 DAG 结点保留在
满足这两个性质的集合
这样我们可以把拟阵对应为原来那张 DAG 上的一个子图,满足所有源点代表集合大小相等且源点在原来的图上能到达的结点都在这个子图中
来个例子:
比如
但是
再举个例子:
假设
一个感性的理解:
我们随便取出一个不是生成树的边集,必定可以再找到一条边,连接两个连通块,这样的边肯定不构成环;随便选出一个不构成环的边集,随便删掉一条肯定也不会出现环
说到这你可能就发现了,有关生成树的最优化问题可以尝试转化为拟阵!
然鹅我们还不知道怎么求解有关拟阵的最优问题(我们甚至没定义拟阵上的最优概念),于是有了独立集的概念
2. 独立集与拟阵最优化问题
我们现在的目标是尝试将一些特殊的最优化问题转化为拟阵上的最优化问题并解决它
首先我们要对
这样拟阵上的最优化问题就可以转化找到为
怎么找到这个集合呢?暴力
我们需要引入一个新的概念:独立集
只要集合
你或许会想:这不新瓶装旧酒吗?你**骗我是吧
别急,还有其他概念:
- 若
\exists x \in M,x \notin A,A \cup \{x \} \in S ,称A 为可拓展的;否则称A 为极大独立集
考虑
还是拿
显然同一拟阵中极大独立集的大小都相等
- 假如
A \in S 的权值是S 中最大的,我们称A 为权值极大独立集
显然权值极大独立集一定是极大独立集
求解权值极大独立集可以用贪心解决(虽说 dmy 讲课时说应该是贪心可以用权值极大独立集解决):
-
将
M 中的元素按照w_i 从大到小排序 -
维护权值极大独立集
I ,初始化I = \emptyset -
枚举
M 中元素x ,只要\{ x \} \cup I 仍然为独立集就将x 加入I
上课写的狗屎板子:
void insert()
{
sort(a+1,a+1+n,cmp);
for (int i=n;i>=1;--i)
if (check(a[i]))
{
add(a[i]);
}
}
来个例题:求最小生成树权值和
直接让
要求
那么我们让
由于权值极大独立集元素个数是固定的(或者说边数是固定的),这样处理对答案的改变只与点数有关,所以是正确的!
别忙着发题解,你会发现这个算法已经有了个名字:Kruskal
是的,我们得到了一个新的 Kruskal 的证明方法
再来个例题:魔法商店
题意:给你
直接令
既然是个拟阵,就可以维护极大独立集了,这个比较简单,用类似线性基的方式将
const long double eps=0.0001;
struct equip
{
ll c;
long double z[505];
bool operator <(const equip &x) const
{
return c<x.c;
}
} a[505];
bool check(equip );
void ins(equip );
ll n,ans,m,l;
bool here[505];
long double b[505][505],o=1.0;
int main()
{
n=read(),m=read();
for (int i=1;i<=n;++i)
for (int j=1;j<=m;++j)
a[i].z[j]=read();
for (int i=1;i<=n;++i)
a[i].c=read();
sort(a+1,a+1+n);
ins(a[1]);
ans+=a[1].c;
for (int i=2;i<=n;++i)
{
if (check(a[i]))
continue;
ans+=a[i].c;
ins(a[i]);
}
for (int i=1;i<=m;++i)
if (here[i])
++l;
write(l,' '),write(ans);
return 0;
}
bool check(equip x)
{
for (int i=1;i<=m;++i)
if (fabs(x.z[i])>eps)
{
if (!here[i])
return false;
long double tmp=x.z[i]/b[i][i];
x.z[i]=0;
for (int j=i+1;j<=m;++j)
x.z[j]-=tmp*b[i][j];
}
return true;
}
void ins(equip x)
{
for (int i=1;i<=m;++i)
if (fabs(x.z[i])>eps)
{
if (!here[i])
{
here[i]=1;
for (int j=i;j<=m;++j)
b[i][j]=x.z[j];
return;
}
long double tmp=x.z[i]/b[i][i];
x.z[i]=0;
for (int j=i+1;j<=m;++j)
x.z[j]-=tmp*b[i][j];
}
}
拟阵的功能远不止这些,在 2018 年国家集训队论文中杨乾澜大神介绍了拟阵的一个更牛马牛逼的扩展:拟阵交
3. 拟阵交
假设有两个拟阵
有些问题要求最优化的答案有多个性质,只看单独某个性质都可以转为拟阵最优化问题,凑一坨却不好做
这时我们可以把每一个性质都抽象成一个拟阵,最后的答案即为拟阵交
然鹅大部分这样的问题都不好做,原因在于:
拟阵交不一定是拟阵
更令人悲观的是,大于等于三个拟阵交问题是 NP-Hard 的,可以规约到哈密顿路径
虽然但是,两个拟阵的交是有多项式算法的(怎么这么像 2-SAT 呢),我们下面就来介绍:
介绍一个新的图:交换图
交换图是一个有向二分图,可以认为左部是
这个图的点为
我们需要一个新的布尔数组
我们现在进行连边:
取出左部一个点
-
若
I' \in S ,连一条x 到y 的有向边,边权为(-1)^{vis_i}w_y -
若
I' \in T ,连一条y 到x 的有向边,边权为(-1)^{vis_i}w_x
再来张图,我们令
现在我们来求拟阵交,使用增广法:
-
初始化
I= \emptyset ,并建立两个新结点s,t -
建立交换图
G_{S,T}(I) -
找到右部所有加入
I 仍使I \in S 的点,记作X_1 -
找到右部所有加入
I 仍使I \in T 的点,记作X_2 -
将
s 与X_1 的所有结点连一条边权为0 有向边,X_2 的所有结点与t 连一条边权为0 的有向边 -
求
s 到t 的最短路,距离相同优先选择经过边数少的,可以用诈尸的SPFA -
假如存在最短路,将这条最短路上的所有元素的
vis_i 变成!vis_i ,然后回到操作 2 循环 -
假如不存在最短路,结束循环,
I 即为所有vis_i=1 的元素(除了s,t 外)
好像复杂度不超过
5. 一些例子
例1:给你一个
令 感性理解
例2:CF1556H
给你一个
这个题复杂一点:
首先,
那么
如果你想将前
考虑这样一个情况(度数限制为
这个情况中,