二分图:
最大匹配:二分图中一个边的集合{E}中,当任意两条边都不相交于同一个节点时,这个边集被称作一个匹配,这个集合最大时,称为一个最大匹配。
最小点覆盖:如果选定一个点,看做选择了它连接的所有边,能涵盖所有边时且节点最少时,称为最小点覆盖。
最大独立集:选出一些点,使得它们两两没有边连接。选择的点最多时,这个集合称作最大独立集。
总点数=最小点覆盖+最大独立集证明
最小点覆盖=最大匹配( König定理)。从右边未匹配的点出发,沿着未匹配-匹配-未匹配去dfs。最终最小点覆盖就是左边出现的点和右边未出现的点。
最小边覆盖=点数-最大匹配。
无向图:
最大独立集=补图的最大团,反之亦然。
例题:传送门
分析,首先要考虑连续的1是没用的,其次,两个1之间的这些条件没办法同时满足。如果建模成图论,往两个点之间连边,要选出一个独立集出来。借用上面的结论,那就是求一个补图的最大团。
#include<bits/stdc++.h>
using namespace std;
const int N = 45;
int nd, nm, G[N][N];
int cntClique, pts[N], res[N], cnt[N];
map<string, int> H;
int n;
vector<int> v;
bool dfs(int pos, int num){
for(int i=pos+1;i<=n;++i){
if(cnt[i]+num<=cntClique)return false;//这里是一处剪枝
if(G[pos][i]){//考虑与当前团节点的关系
int ok=1;
for(int id=1;id<=num;++id){
if(!G[i][pts[id]]){
ok=0;break;
}
}
if(ok){
pts[num+1]=i;
if(dfs(i,num+1))return true;
}
}
}
if(num>cntClique){ //多枚举一个,最多只扩充一个点
for(int i=1;i<=num;++i){
res[i]=pts[i];
}
cntClique=num;
return true;
}
return false;
}
void maxClique(){
cntClique=-1;
for(int i=n;i>0;--i){
pts[1]=i;
dfs(i,1);
cnt[i]=cntClique;
}
}
int main(){
ios_base::sync_with_stdio(0);
cin.tie(0);
memset(G,1,sizeof G);
cin>>nd>>nm;
for(int i=1;i<=nd;++i){
int op;string s;
cin>>op;
if(op==1){
v.clear();
}else{
cin>>s;
if(!H.count(s)){
H[s]=++n;
}
/*n^2建图跑的飞起,cf数据有问题*/
int y=H[s];
for(auto& x:v){
G[x][y]=0;
G[y][x]=0;
}
v.push_back(y);
}
}
maxClique();
if(cntClique<0){
cout<<"0";
}else{
cout<<cntClique;
}
return 0;
}