Loading

拟阵小记

一般定义(用独立集定义拟阵)

定义拟阵为二元组 \((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;
}
posted @ 2024-04-27 21:35  Sktn0089  阅读(1)  评论(0编辑  收藏  举报