二分图
二分图的判定
定义
一张图可以分成两个点集 \(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();
}