二分图

二分图的判定

定义

一张图可以分成两个点集 \(S,T\) 不重不漏,而且每个点集的内部节点之间没有直接相连的边,则称该图为二分图。

判定

定理:图中不存在奇数边数的环,它就是二分图。

使用染色法判定。每个节点可以被染成颜色1或-1,如果这个节点是1色(-1色同理)与他直接相连的就是-1,如果一个时刻在更新的时候发现这个点又要是1又要是-1这样矛盾那就不是二分图。其余就是二分图。

二分图最大匹配

匈牙利算法。

最坏时间复杂度:

  • \(O(n^3)\)(邻接矩阵)
  • \(O(nm)\)(邻接表)

主函数枚举每一个节点\(x\),尝试为这个节点找到匹配边,找到了就答案++

假如找边碰壁(相到的点\(y\)已有匹配),就看那个跟\(y\)匹配的节点\(z\)能不能不跟\(y\)而跟其他点有alternative的匹配,可以的话,我们就能把\(x\)\(y\)匹配了

代码实现

#include <bits/stdc++.h>
using namespace std;
const int N=505;
int n,m,k,ans,e[N][N],mat[N],vis[N];
bool dfs(int x){
	for(int i=1;i<=m;i++)
		if(e[x][i]&&!vis[i]){
			vis[i]=1;
			if(!mat[i]||dfs(mat[i])){mat[i]=x;return 1;}
		}
	return 0;
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1,u,v;i<=k;i++){
		scanf("%d%d",&u,&v);
		e[u][v]=1;
	}
	for(int i=1;i<=n;i++){
		memset(vis,0,sizeof(vis));
		ans+=dfs(i);
	}
	cout<<ans;
}

二分图最小点覆盖

定义:求点数最少的点集\(S\)满足\(S\)内节点所引出的边集的并集等于总边集

求法:二分图最小点覆盖=二分图最大匹配

二分图最大独立集

定义:求点数最多的点集\(S\)满足\(S\)内节点两两不相邻

求法:最大点数=节点总数-二分图最小点覆盖

一般图的最大团

定义:求点数最多的点集\(S\)满足\(S\)内节点和边构成了完全图

求法:最大团=节点总数-补图最大匹配
(注:如果补图是二分图就容易用匈牙利求)

DAG 的最小不交路径覆盖

定义:用最小数量的不交路径覆盖 DAG 中所有节点

求法:DAG的最小路径覆盖数=DAG图中的节点数-相应二分图中的最大匹配数.
(注:二分图的建立方式——对于 DAG 中每一条边 \(x\to y\),建立 \(x\to y'\)

满足传递闭包时,最小不交链覆盖 = 最大独立集 = n - 拆点二分图最大匹配
不满足时,最小可交链覆盖 = 由传递闭包构成的新图的最小不交链覆盖

Hall 定理

二分图完全匹配(左侧所有点都存在匹配)的充要条件是:对于左侧所有点构成的全集 \(U\) 的所有子集 \(S\subseteq U\)\(|Neigbour(S)|\ge|S|\)

练习:(跟线段树结合)bzOJ3639.圆桌会议
题解:“人的所有子集”的条件不好处理,我们发现它其实等价于“位置的所有区间”满足 \(r-l+1\ge sum\_a(l,r)\)。先断环为链,对于 \(l_i\ge 0 \operatorname{and} r_i< m\) 的区间要复制一份到后面。在序列上枚举 \(r\),首先上面的式子容易想到变形成 \(r-(l-1)\ge s_r-s_{l-1}\)\(\forall i\le r-1,r-s_r\ge i-s_i\) 的形式,线段树维护即可。
注意:须特判 sumall>m 的情况!

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+5;
int n,m,u,arr[N];
map<int,int>mp;
struct Segment {int l,r,v;}a[N];
struct node {int s,tag;}t[N<<2];
void pushup(int k){
	t[k].s=max(t[k<<1].s,t[k<<1|1].s);
}
void pushdown(int k){
	if(!t[k].tag)return;
	t[k<<1].tag+=t[k].tag,t[k<<1|1].tag+=t[k].tag;
	t[k<<1].s-=t[k].tag,t[k<<1|1].s-=t[k].tag;
	t[k].tag=0;
}
void build(int l,int r,int k){
	if(l==r){t[k].s=arr[l];t[k].tag=0;return;}
	t[k].tag=0;
	int mid=l+r>>1;
	build(l,mid,k<<1),build(mid+1,r,k<<1|1);
	pushup(k);
}
void chg(int L,int R,int v,int l,int r,int k){
	if(L<=l&&r<=R){t[k].s-=v;t[k].tag+=v;return;}
	pushdown(k);
	int mid=l+r>>1;
	if(L<=mid)chg(L,R,v,l,mid,k<<1);
	if(R>mid)chg(L,R,v,mid+1,r,k<<1|1);
	pushup(k);
}
int ask(int L,int R,int l,int r,int k){
	if(L>R)return -1e18;
	if(L<=l&&r<=R)return t[k].s;
	pushdown(k);
	int mid=l+r>>1,mx=-1e18;
	if(L<=mid)mx=max(mx,ask(L,R,l,mid,k<<1));
	if(R>mid)mx=max(mx,ask(L,R,mid+1,r,k<<1|1));
	return mx;
}
void solve(){
	mp.clear();u=0;
	scanf("%d%d",&n,&m);
	int nn=n;int sum=0;
	for(int i=1;i<=n;i++){
		scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].v);
		sum+=a[i].v;
		a[i].l++,a[i].r++;
		if(a[i].l>a[i].r)a[i].r+=m;
		else a[++nn].l=a[i].l+m,a[nn].r=a[i].r+m,a[nn].v=a[i].v;
	}
	if(sum>m){puts("No");return;}
	sort(a+1,a+nn+1,[](Segment a,Segment b){return a.r<b.r;});
	arr[++u]=1;
	for(int i=1;i<=nn;i++){arr[++u]=a[i].l,arr[++u]=a[i].l-1,arr[++u]=a[i].r,arr[++u]=a[i].r-1;if(a[i].r-m>=1)arr[++u]=a[i].r-m,arr[++u]=a[i].r-m-1;}
  //离散化是本题难点,因为关键点不止a[i].l,a[i].r,还有a[i].l-1,a[i].r-1等
	sort(arr+1,arr+u+1),u=unique(arr+1,arr+u+1)-arr-1;
	for(int i=1;i<=u;i++)mp[arr[i]]=i;
	build(1,u,1);
	for(int i=1;i<=nn;i++){
		chg(mp[a[i].l],u,a[i].v,1,u,1);
		int p=(a[i].r<=m)?1:mp[a[i].r-m];
		if(max((int)0,ask(p,mp[a[i].r-1],1,u,1))>ask(mp[a[i].r],mp[a[i].r],1,u,1))
			{puts("No");return;}
	}
	puts("Yes");
}
signed main(){
	int T;cin>>T;while(T--)solve();
}
posted @ 2021-09-15 21:58  pengyule  阅读(52)  评论(0编辑  收藏  举报