拟阵小记
一般定义(用独立集定义拟阵)
定义拟阵为二元组 \((S,I)\),其中 \(S\) 为一个有限集,\(I\) 为 \(S\) 的子集族上的一个集合,我们称 \(I\) 中的元素为独立集。
其中 \(I\) 满足以下性质:
-
遗传性:\(\forall A\in I,B\subseteq A\Rightarrow B\in I\)
-
扩张性:\(\forall A,B\in I,|A|<|B| \Rightarrow \exists x \in B\backslash A,\ A\cup\{x\}\in I\)
用基定义拟阵
现有一个集合 \(S\),以及一个子集族集合 \(I\)。
定义:
-
基底:\(S\) 的一个极大独立集合,满足该集合中加入其他任何元素后不是基底。换句话说,该集合大小为 \(\max\limits_{A\in I} |A|\)。
-
基:所有基底构成的集合 \(B\)。
那么 \(I=\cup _{A\in B} \cup _{T\subseteq A} T\)。
-
基交换定理:对于任意两个基 \(A_1,A_2 \in B\),有 \(\forall x\in A_1,\ \exists y\in (A_2\backslash A_1),\ A_1\backslash\{x\}\cup \{y\} \in B\)
-
强 · 基交换定理:在上面的基础上,\(A_2\backslash \{y\}\cup \{x\}\) 也是基。
拟阵的环
- 定义:拟阵 \(M=(S,I)\) 的环为所有极小非独立集的集合,记为 \(C(M)\)。
即若 \(A\in C(M)\),那么 \(A\not\in I\) 且 \(\forall x\in A,\ (A\backslash \{x\})\in I\)。
拟阵的秩函数
对于 \(A\subseteq S\),定义秩函数 \(r(A)=\max\limits_{B\in I,B\subseteq A} |B|\)。
性质:
-
有界性:\(r(A)\in [0,|A|]\)
-
单调性:\(\forall A,B\in I, A\subseteq B\Rightarrow r(A)\le r(B)\)
-
次模性:\(r(A\cup B)+r(A\cap B)\le |A|+|B|\)
最大权独立集问题
对于单个元素 \(x\) 都权值 \(w_x\)。
对于 \(A\in I\),定义 \(f(A)=\sum\limits_{x\in A} w_x\),求 \(\max\limits_{A\in I} f(A)\)。
考虑贪心,维护集合 \(A\),初始为空集。
把所有元素 \(x\) 按 \(w_x\) 从大到小排序,然后扫描每个 \(x\),如果可以加入 \(A\) 则加入。
证明:
-
首先加入的元素个数肯定最多,否则不满足拟阵的扩展性。
-
设最终得出的为 \(A\),真实答案为 \(A_1\)。根据交换公理,我们可以替换一个元素,而所有元素已经排好序,矛盾。
例子
一个很简单的例子:最小生成树的 Kruskal 算法。
定义 \((S,I)\),其中 \(S\) 为原图边集,\(I\) 为选出来不成环的边集集合。
其中 \(I\) 满足
-
遗传性:对于合法的 \(A\in I\),且 \(B\subseteq A\),显然有 \(B\in I\)。
-
扩张性:若 \(A,B\in I\) 且 \(|A|<|B|\),我们显然可以从 \(B\) 中挑选一条边连接 \(A\) 中两个不同的连通块。
因此 \((S,I)\) 是拟阵。于是可以把所有边排序,然后一个一个加入。
闭包算子
拟阵 \(M=(S,I)\),对于 \(A\subseteq S\),定义 \(A\) 的闭包算子 \(cl(A)=\{e\in S|r(A\cup \{e\})=r(A)\}\)
- 引理:对于 \(A\subseteq S\),\(\forall e,cl(A)=cl(A\cup \{e\})\)。
拟阵交
给出两个拟阵 \(M_1=(S_1,I_1),M_2=(S_2,I_2)\),求 \(\max\limits_{A\in I_1\cap I_2} |A|\)。
注意到 \(I_1\cap I_2\) 不一定是拟阵,所以不能直接套用上面的做法。
- 最小最大定理:\(\max\limits_{A\in I_1\cap I_2} |A| = \min\limits_{T\subseteq S} \{r_1(T)+r_2(S\backslash T)\}\)
首先我们脑补一下可以发现一定有 \(|A|=|A\cap T|+|A\cap (S\backslash T)|\le r_1(T)+r_2(S\backslash T)\)。
下面来构造一组合法的方案。
考虑增量法,逐步给 \(A\) 加入新的元素,并且给 \(A\) 找到相应的 \(T\) 满足上式。
构造一张有向二分图(记为 \(G(I)\)),点集分别为 \(A,S\backslash A\)。
对于 \(x\in A, y\in S\backslash A\),若 \((T\backslash \{x\}\cup\{y\})\in I_1\),连边 \(x\to y\)。
对于 \(x\in A, y\in S\backslash A\),若 \((T\backslash \{x\}\cup\{y\})\in I_2\),连边 \(y\to x\)。
令 \(S_1=\{x| (A+\{x\})\in I_1\}\),\(S_2\) 同理。我们找一条从 \(S_1\) 任意一点到 \(S_2\) 任意一点的最短路,设 \(P\) 为路径上的点集,那么 \(A\gets A\oplus P\)。
构造 \(T\) 为最终 \(G(I)\) 中能(直接或间接)到达 \(S_2\) 的点的点集。
带权拟阵交
即把 \(|A|\) 换成了 \(f(A)=\sum\limits_{x\in A} w_x\)。
考虑增广时,加入点视为正权值,去掉点视为负权值,以权值和为第一关键字找最长路,以边数为第二关键字找最短路。
使用 SPFA 即可。
例题
CF1556H DIY Tree
首先生成树森林的边集是一个拟阵。
但是带度数限制的边集并不是拟阵。考虑原因,我们只需要枚举前 \(k\) 个点的连边情况,剩下的边集就是拟阵了。
把两个拟阵求个最小权的带权拟阵交即可。
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned ll
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=55, M=2510;
ll n,k,d[maxn],a[maxn][maxn],ans,deg[maxn];
struct DSU{
ll fa[maxn];
ll find(ll x) {return fa[x]==x? x:fa[x]=find(fa[x]);}
bool merg(ll x,ll y){
x=find(x), y=find(y);
if(x==y) return 0;
fa[x]=y; return 1;
}
}dsu[M];
vector<ll> to[M];
ll ex[M], ey[M], vis[M], idcnt, tot;
bool chk1(ll x,ll y) {return dsu[x].find(ex[y])!=dsu[x].find(ey[y]);}
bool chk1(ll x) {return dsu[0].find(ex[x])!=dsu[0].find(ey[x]);}
bool chk2(ll x,ll y){
if(ex[y]>k) return 0;
return deg[ex[y]]-(ex[x]==ex[y])+1<=d[ex[y]];
}
bool chk2(ll x) {return ex[x]>k||deg[ex[x]]+1<=d[ex[x]];}
void add(ll i){
vis[i]=1;
if(ex[i]<=k) ++deg[ex[i]];
}
void del(ll i){
vis[i]=0;
if(ex[i]<=k) --deg[ex[i]];
} ll w[M];
queue<ll>q; ll dis[M], pre[M], inq[M], len[M], _s, _t;
void DSU_Init(){
for(ll i=1;i<=n;i++){
dsu[0].fa[i]=i;
for(ll j=tot+1;j<=idcnt;j++) dsu[j].fa[i]=i;
}
for(ll i=1;i<=idcnt;i++)
if(vis[i]) dsu[0].merg(ex[i],ey[i]);
for(ll i=tot+1;i<=idcnt;i++)
if(vis[i])
for(ll j=1;j<=idcnt;j++)
if(vis[j]&&i!=j) dsu[i].merg(ex[j],ey[j]);
}
bool spfa(){
for(ll i=tot+1;i<=idcnt+2;i++) pre[i]=inq[i]=0, dis[i]=1e17;
dis[_s]=len[_s]=0, q.push(_s);
while(!q.empty()){
ll u=q.front(); q.pop(), inq[u]=0;
for(ll v:to[u]){
if(dis[v]>dis[u]+w[v]||dis[v]==dis[u]+w[v]&&len[v]>len[u]+1){
dis[v]=dis[u]+w[v], pre[v]=u, len[v]=len[u]+1;
if(!inq[v]) inq[v]=1, q.push(v);
}
}
}
if(dis[_t]>1e16) return 0;
for(ll x=pre[_t];x!=_s;x=pre[x])
if(vis[x]) del(x);
else add(x); return 1;
}
void solve(){ _s=idcnt+1, _t=_s+1;
do{
DSU_Init();
for(ll i=tot+1;i<=idcnt;i++)
w[i]=a[ex[i]][ey[i]]*(vis[i]? -1:1), to[i].clear();
to[_s].clear(), to[_t].clear();
for(ll i=tot+1;i<=idcnt;i++)
if(vis[i])
for(ll j=tot+1;j<=idcnt;j++)
if(!vis[j]){
if(chk1(i,j)) to[i].pb(j);
if(chk2(i,j)) to[j].pb(i);
}
for(ll i=tot+1;i<=idcnt;i++){
if(vis[i]) continue;
if(chk1(i)) to[_s].pb(i);
if(chk2(i)) to[i].pb(_t);
}
} while(spfa());
}
int main(){
scanf("%lld%lld",&n,&k);
for(ll i=1;i<=k;i++) scanf("%lld",d+i);
for(ll i=1;i<=n;i++)
for(ll j=i+1;j<=n;j++){
scanf("%lld",a[i]+j);
}
for(ll i=1;i<=k;i++)
for(ll j=i+1;j<=k;j++)
ex[++tot]=i, ey[tot]=j;
idcnt=tot; ans=1e17;
for(ll i=1;i<=n;i++)
for(ll j=max(i,k)+1;j<=n;j++)
ex[++idcnt]=i, ey[idcnt]=j;
for(ll S=0;S<(1<<tot);S++){
for(ll i=1;i<=idcnt;i++) vis[i]=0;
for(ll i=1;i<=n;i++) dsu[0].fa[i]=i; ll ok=1;
for(ll i=1;i<=k;i++) deg[i]=0;
for(ll i=1;i<=tot;i++)
if(S&(1<<i-1)){ ++deg[ex[i]], ++deg[ey[i]], vis[i]=1;
if(!dsu[0].merg(ex[i],ey[i])){
ok=0; break;
}
}
for(ll i=1;i<=k;i++)
if(deg[i]>d[i]) {ok=0; break;}
if(!ok) continue;
solve(); ll sum=0, cnt=0;
for(ll i=1;i<=idcnt;i++)
if(vis[i]) sum+=a[ex[i]][ey[i]], ++cnt;
if(cnt==n-1) ans=min(ans,sum);
}
printf("%lld",ans);
return 0;
}