拟阵拟阵交

本文参考2018集训队论文《浅谈拟阵的一些拓展及其应用》
本文只有结论无证明,证明参考《浅谈拟阵的一些拓展及其应用》。

定义

对于一个集合 S {S} S,如果 S S S的子集 T T T满足某种特殊性质,则将 T T T称为独立集,特别地,将空集称为独立集。

定义拟阵 M = ( S , I ) M=(S,I) M=(S,I)表示,其中 I I I为所有独立集的集合,并且 I I I需要满足两个性质才能被称为拟阵:

  1. 遗传性:如果 T ∈ I T\in I TI,则任意 T {T} T的子集 P P P都要满足 P ∈ I P\in I PI
  2. 交换性:如果 A , B ∈ I A,B\in I A,BI ∣ A ∣ < ∣ B ∣ |A|<|B| A<B,则存在 x ∈ B / A x\in B/A xB/A使得 A + { x } ∈ I A+\{x\}\in I A+{x}I

只要定义的独立集满足拟阵的性质就可以用以下拟阵算法。

最大权独立集

对于拟阵 M = ( S , I ) M=(S,I) M=(S,I),设 S = { s 1 , s 2 , . . . , s n } {S=\{s_1,s_2,...,s_n\}} S={s1,s2,...,sn},每个元素 s i {s_i} si都有一个权值 w ( s i ) {w(s_i)} w(si),求一个最大的 T ∈ I {T\in I} TI,使得 ∑ e ∈ T w ( e ) \sum\limits_{e\in T}w(e) eTw(e)最大化。
贪心算法:
w ( s i ) {w(s_i)} w(si)从大到小将点集排序,设排序后的点集为 { e 1 , e 2 , . . . , e n } \{e_1,e_2,...,e_n\} {e1,e2,...,en},设最开始 T = ∅ T=\varnothing T=
依次遍历 { e 1 , e 2 , . . . , e n } \{e_1,e_2,...,e_n\} {e1,e2,...,en},遍历到 e i {e_i} ei时,如果 T + { e i } {T+\{e_i\}} T+{ei}是独立集,则将 { e i } \{e_i\} {ei}加入到集合 T {T} T
典型的例子是 k r u s k a l kruskal kruskal求最小生成树。

拟阵交

对于两个拟阵 M 1 = ( S , I 1 ) , M 2 = ( S , I 2 ) M_1=({S,I_1}),M_2=(S,I_2) M1=(S,I1),M2=(S,I2),求一个 I ∈ I 1 ∩ I 2 {I\in I_1\cap I_2} II1I2
求交算法:
令初始 I = ∅ {I=\varnothing} I=

  1. I {I} I建二分图,如果 x ∈ T , y ∈ S / T x\in T,y\in S/T xT,yS/T满足 T − { x } + { y } ∈ I 1 T-\{x\}+\{y\}\in I_1 T{x}+{y}I1,则建立 x → y x\to y xy的有向边。如果 x ∈ T , y ∈ S / T x\in T,y\in S/T xT,yS/T满足 T − { x } + { y } ∈ I 2 T-\{x\}+\{y\}\in I_2 T{x}+{y}I2,则建立 y → x y\to x yx的有向边。
  2. 定义 X 1 = { x ∈ S / I : I + { x } ∈ I 1 } , X 2 = { x ∈ S / I : I + { x } ∈ I 2 } X_1=\{x\in S/I: I+\{x\}\in I_1\},X_2=\{x\in S/I: I+\{x\}\in I_2\} X1={xS/I:I+{x}I1},X2={xS/I:I+{x}I2}。找一条从 X 1 {X_1} X1任意一点到 X 2 {X_2} X2任意一点的最短路径,令路径上的点集为 P P P,让 I = ( I ∪ P ) − ( I ∩ P ) {I=(I\cup P)-(I\cap P)} I=(IP)(IP)

1 , 2 1,2 1,2的过程称为一次增广,重复增广过程直到找不到路径,就找到了最大的 I I I

最大带权拟阵交

如果每个点 s 1 ∈ S s_1\in S s1S带有点权 w ( s 1 ) w(s_1) w(s1),在建图时如果 x ∈ I x\in I xI,则其权值设为 − w ( x ) -w(x) w(x),如果 x ∈ S / I x\in S/I xS/I,则权值设为 w ( x ) w(x) w(x)
找最短路时以权值最小为第一关键字,路径长度最小为第二关键字。

这里找最大带权在最短路里却以权值最小为关键字基于最小最大定理,不要觉得奇怪。

CF1556H. DIY Tree
n {n} n个点的边集为 S {S} S
枚举前 k {k} k个点的生成森林 T {T} T

构造拟阵 M 1 = ( S , I 1 ) {M_1=(S,I_1)} M1=(S,I1) I 1 = { A : A ∪ T 无 环 } I_1=\{A:A\cup T无环\} I1={A:AT}
遗传性:设 A {A} A是独立集( A ∪ T {A\cup T} AT无环),删除 A {A} A中的边 A ∪ T {A\cup T} AT仍然无环。
交换性:设 A , B A,B A,B是独立集且 ∣ A ∣ > ∣ B ∣ {|A|>|B|} A>B,显然 A , B A,B A,B都是森林,由于 ∣ A ∣ > ∣ B ∣ {|A|>|B|} A>B,故 A A A中的连通分量比 B B B中的连通分量少,因此 A A A中存在一条边连接 B B B中的某两个连通分量,故存在 x ∈ A / B x\in A/B xA/B使得 B + { x } B+\{x\} B+{x}也是独立集。

构造拟阵 M 2 = ( S , I 2 ) {M_2=(S,I_2)} M2=(S,I2) I 2 = { A : A ∪ T 中 { 1 , 2 , . . . , k } 的 度 数 不 超 过 { d 1 , d 2 , . . . , d k } } I_2=\{A:A\cup T中\{1,2,...,k\}的度数不超过\{d_1,d_2,...,d_k\}\} I2={A:AT{1,2,...,k}{d1,d2,...,dk}}
遗传性:设 A {A} A是独立集,删除 A A A中的边度数只会减少,故 A A A的子集仍然是独立集。
交换性:设 A , B A,B A,B是独立集且 ∣ A ∣ > ∣ B ∣ {|A|>|B|} A>B。如果 A / B A/B A/B中存在一条边两端都是非关键点,则可以添加这条边到 B B B中使得 B B B仍然是独立集。否则, A A A中存在一个关键点的度数大于 B B B中的这个关键点,选择这个关键点的边加入 B B B B B B仍然是独立集。故存在 x ∈ A / B x\in A/B xA/B使得 B + { x } B+\{x\} B+{x}也是独立集。

求两个拟阵的最大带权交。

#include <bits/stdc++.h>
typedef unsigned long long ull;
typedef long long ll;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define nep(i, r, l) for (int i = r; i >= l; i--)
void sc(int &x) { scanf("%d", &x); }
void sc(int &x, int &y) { scanf("%d%d", &x, &y); }
void sc(int &x, int &y, int &z) { scanf("%d%d%d", &x, &y, &z); }
void sc(ll &x) { scanf("%lld", &x); }
void sc(ll &x, ll &y) { scanf("%lld%lld", &x, &y); }
void sc(ll &x, ll &y, ll &z) { scanf("%lld%lld%lld", &x, &y, &z); }
void sc(char *x) { scanf("%s", x); }
void sc(char *x, char *y) { scanf("%s%s", x, y); }
void sc(char *x, char *y, char *z) { scanf("%s%s%s", x, y, z); }
void out(int x) { printf("%d\n", x); }
void out(ll x) { printf("%lld\n", x); }
void out(int x, int y) { printf("%d %d\n", x, y); }
void out(ll x, ll y) { printf("%lld %lld\n", x, y); }
void out(int x, int y, int z) { printf("%d %d %d\n", x, y, z); }
void out(ll x, ll y, ll z) { printf("%lld %lld %lld\n", x, y, z); }
void ast(ll x,ll l,ll r){assert(x>=l&&x<=r);}
using namespace std;
#define inf 0x3f3f3f3f
#define fi first
#define se second
const int N=55,mod=1e9+7;
int n,k,d[N],sd[N],ud[N],a[N][N];
vector<pair<int,int>>edge,all;
int ans=inf;
struct dsu
{
	vector<int>f;
	void init(int n){f.clear();rep(i,0,n) f.push_back(i);}
	int getf(int x){return f[x]==x?x:f[x]=getf(f[x]);}
	void uni(int u,int v){f[getf(u)]=getf(v);}
}f1,f2;
vector<pair<int,int>>T,S,x1,x2;
bool vis[N][N];
vector<pair<int,int>>e[N][N];
pair<int,int>dis[N][N];
pair<int,int>pre[N][N];
bool inq[N][N];
bool aug(vector<pair<int,int>>&T)
{
	x1.clear();x2.clear();S.clear();
	f2=f1;
	rep(i,1,n) d[i]=sd[i];
	for(pair<int,int>&x:T)
		assert(!(x.first<=k&&x.second<=k)),f2.uni(x.first,x.second),d[x.first]++,d[x.second]++;
	for(pair<int,int>&g:all)
	{
		int i=g.first,j=g.second;
		if(!vis[i][j])
		{
			S.push_back({i,j});
			if(f2.getf(i)!=f2.getf(j))
				x1.push_back({i,j});
			if(d[i]<ud[i]&&d[j]<ud[j])
				x2.push_back({i,j});
		}
	}
	for(pair<int,int>&g:all)
	{
		int i=g.first,j=g.second;
		e[i][j].clear();
		dis[i][j]={inf,inf};
		pre[i][j]={0,0};
		inq[i][j]=false;
	}
	for(pair<int,int>&x:T)
	{
		f2=f1;
		for(pair<int,int>&y:T)
			if(y!=x) f2.uni(y.fi,y.se);
		for(pair<int,int>&y:S)
		{
			if(f2.getf(y.fi)!=f2.getf(y.se))
				e[x.fi][x.se].push_back({y.fi,y.se});
			d[x.fi]--;d[x.se]--;
			if(d[y.fi]<ud[y.fi]&&d[y.se]<ud[y.se])
				e[y.fi][y.se].push_back({x.fi,x.se});
			d[x.fi]++;d[x.se]++;
		}
	}
	queue<pair<int,int>>q;
	for(pair<int,int>&x:x1)
	{
		inq[x.fi][x.se]=true;
		dis[x.fi][x.se]={a[x.fi][x.se]*(vis[x.fi][x.se]?-1:1),1};
		q.push({x.fi,x.se});
	}
	while(!q.empty())
	{
		pair<int,int>u=q.front();q.pop();
		inq[u.fi][u.se]=false;
		int x=dis[u.fi][u.se].fi,y=dis[u.fi][u.se].se;
		y++;
		for(pair<int,int>&v:e[u.fi][u.se])
		{
			if(dis[v.fi][v.se]>make_pair(x+a[v.fi][v.se]*(vis[v.fi][v.se]?-1:1),y))
			{
				dis[v.fi][v.se]=make_pair(x+a[v.fi][v.se]*(vis[v.fi][v.se]?-1:1),y);
				pre[v.fi][v.se]=u;
				if(!inq[v.fi][v.se])
					q.push({v.fi,v.se}),inq[v.fi][v.se]=true;
			}
		}
	}
	pair<int,int>td;
	pair<int,int>mn={inf,inf};
	for(pair<int,int>&x:x2)
		if(dis[x.fi][x.se]<mn)
		{
			mn=dis[x.fi][x.se];
			td=x;
		}
	if(mn==make_pair(inf,inf)) return false;
	while(td.fi)
	{
		vis[td.fi][td.se]^=1;
		td=pre[td.fi][td.se];
		T.clear();
		for(pair<int,int>&g:all)
		{
			int i=g.first,j=g.second;
			if(vis[i][j]) T.push_back({i,j});
		}
	}
	return true;
}
int sol()
{
	T.clear();
	for(pair<int,int>&g:all)
	{
		int i=g.first,j=g.second;
		vis[i][j]=false;
	}
	while(aug(T));
	f2=f1;
	for(pair<int,int>&x:T) f2.uni(x.first,x.second);
	rep(i,2,n) if(f2.getf(1)!=f2.getf(i)) return inf;
	int res=0;
	for(pair<int,int>&x:T) res+=a[x.first][x.second];
	return res;
}
int dt=0;
void dfs(int p,int res,dsu f)
{
	if(p==edge.size())
	{
		dt++;
		f1=f;
		int sum=sol();
		ans=min(ans,res+sum);
		return;
	}
	dfs(p+1,res,f);
	int x=edge[p].fi,y=edge[p].se;
	if(f.getf(x)!=f.getf(y)&&sd[x]<ud[x]&&sd[y]<ud[y])
	{
		f.uni(x,y);
		sd[x]++;sd[y]++;
		dfs(p+1,res+a[x][y],f);
		sd[x]--;sd[y]--;
	}
}
bool cmp(pair<int,int>&x,pair<int,int>&y)
{
	return a[x.fi][x.se]<a[y.fi][y.se];
}
void sol(int cas)
{
	sc(n,k);
	rep(i,1,k) sc(ud[i]);
	rep(i,k+1,n) ud[i]=n;
	rep(i,1,n)
		rep(j,i+1,n)
		sc(a[i][j]),a[j][i]=a[i][j];
	rep(i,1,k)
		rep(j,k+1,n)
		all.push_back({i,j});
	vector<pair<int,int>>g;
	f2.init(n);
	rep(i,k+1,n)
		rep(j,i+1,n)
			g.push_back({i,j});
	sort(g.begin(),g.end(),cmp);
	for(pair<int,int>&x:g)
		if(f2.getf(x.fi)!=f2.getf(x.se))
		{
			f2.uni(x.fi,x.se);
			all.push_back(x);
		}
	rep(i,1,k)
		rep(j,i+1,k)
		edge.push_back({i,j});
	dsu f;
	f.init(n);
	dfs(0,0,f);
	out(ans);
}
int main()
{
  // freopen("1.in", "r",stdin);
  // freopen("1.out", "w", stdout);
  srand(time(0));
  int t=1,cas=0;
//   scanf("%d",&t);
  while(t--)
  {
    sol(++cas);
  }
  return 0;
}
/*
befor submit code check:
freopen
size of N
mod
debug output
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值