二分图匈牙利算法

一张图是二分图们当且仅当它的点可以被分成两部分,而所有的变的两个端点都分属不同部分,分为左部和右部。

一张图的一个匹配是一些没有公共端点的边。

匈牙利算法是枚举每一个左部点x没然后枚举x所连的边,对应出点y,若y没有被先前的左点匹配,直接将x匹配y,否则尝试让y的原配左点去匹配其他右点,若原配匹配到了其他点,就将x匹配y,否则x失配。

时间复杂度O(n*e+m),n是左点个数,e是边数,m是右点个数,若右点个数远大于左点个数,可以交换左右部。

二分图的关键在于建图和连边。

最小顶点覆盖是指选择最少的点来覆盖所有的边。最小顶点覆盖=二分图的最大匹配。

最大独立集是指选出最多的顶点使得这些顶点两两不相邻。最大独立集=所有顶点数-最小顶点覆盖

最大团是指一个最大的点集,使得该集合内任意两点都有边相连。最大团=补图的最大独立集。

最小路径覆盖是指在图中找最少的路径来覆盖所有顶点,分为相交路径和不相交路径,最小不相交路径=所有顶点数-最大匹配数,自小相交路径=所有顶点数-floyed传递闭包后的最大匹配数。

染色判断是否是二分图

bool dfs(int x,int c){
    col[x]=c;
    for(int i=h[x];i;i=e[i].next){
        int y=e[i].to;
        if(col[y]==col[x])return false;
        if(!col[y]&&!dfs(y,-c))return false;
    }
    return true;
}
inline bool check(){
    for(int i=1;i<=n;i++)
    if(!col[i]&&!dfs(i,1))return false;
    return true;
}
int n,m,match[N],vis[N],h[N],tot,to[N];
struct edge{
    int to,next;
}e[N];
inline void add(int x,int y){
    e[++tot]={y,h[x]};h[x]=tot;
}
bool dfs(int x,int t){
    for(int i=h[x];i;i=e[i].next){
        int y=e[i].to;
        if(vis[y]==t)continue;/*y已经访问过则跳过*/
        vis[y]=t;/*标记访问*/
        if(!match[y]/*自己还没有匹配*/||dfs(match[y],t)/*或者自己原配可以进行更换*/){
            match[y]=x;/*进行匹配*/
            to[x]=y;
            return true;
        }
    }
    return false;
}
inline int hungary(int n){
    int ans=0;
    memset(match,0,sizeof(match));
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++){
        ans+=dfs(i,i);/*使用时间戳来避免memset*/
    }
    return ans;
}
inline int hungary(int n){
    int ans=0;
    memset(match,0,sizeof(match));
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++){
        if(dfs(i,i))ans++;
        else break;/*如果答案需要连续性,不符合时则break*/
    }
    return ans;
}

每个装备有两个属性,使用每种装备只能使用一个属性,每种装备最多能用一次,现要求属性连续递增使用,1,2,3...,求对多连续次数。每个装备只能用一次,每个物品只有一个有用的属性值,每个属性值只对应一个有用的物品,同时在枚举左部点时是枚举属性值,当无法匹配时直接终止,把属性值和物品连边。

    for(int i=1;i<=n;i++){
        int a,b;
        cin>>a>>b;
        add(a,i),add(b,i);
    }
    cout<<hungary(10000);

n人,已知是否是本校学生,是否回家,问是否存在一方案使得所有不回家的本校学生和所有来看他们的人都有地方住,每个人只能睡自己的床和自己直接认识的人的床。一个人对应一张床,若这个人是本校学生并且留校,自己和自己的床连边,若这个人a认识另一个人b且b是本校学生,那么a可以睡b的床,a向b的床连边,对每个人跑匈牙利算法,统计需要床的人数和最大匹配数是否相同。

    while(t--){
        int cnt=tot=0,num=0;
        memset(h,0,sizeof(h));
        memset(vis,0,sizeof(vis));
        memset(match,0,sizeof(match));
        cin>>n;
        for(int i=1;i<=n;i++)cin>>school[i];
        for(int i=1;i<=n;i++){
            cin>>home[i];
            if(!home[i]&&school[i])add(i,i),cnt++;
        }
        for(int i=1;i<=n;i++)cnt+=school[i]^1;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                int x;
                cin>>x;
                if(x&&school[j])add(i,j);
            }
        }
        for(int i=1;i<=n;i++){
            if((school[i]&&!home[i])||!school[i]){
                memset(vis,0,sizeof(vis));
                num+=dfs(i,i);
            }
        }
        cout<<(num==cnt?"^_^\n":"T_T\n");
    }

车上n排座位,共有2*n个人,每排座位可以坐两人,每个人都有自己想坐的两个排数,求最多多少人可以如愿。每一排能坐两个人,把一排拆成两个点,x和x+n,再求最大匹配。

signed main(){
    cin>>n;
    for(int i=1;i<=(n<<1);i++){
        int a,b;
        cin>>a>>b;
        add(i,a),add(i,b);
        add(i,a+n),add(i,b+n);
    }
    cout<<hungary(n<<1);
    return 0;
}

m道题n种不同的锦囊妙计,每道题可以从两种锦囊妙计种选择一种,每种锦囊妙计只能用一次,使用后进入下一题,每道题都要用锦囊妙计,求最多能通过的题数。每道题的编号和自己的锦囊妙计连边,之后对每道题跑匈牙利,不能匹配时直接终止,匹配时记录每个点匹配到的锦囊妙计。

inline int hungary(int n){
    int ans=0;
    for(int i=1;i<=n;i++){
        if(dfs(i,i))ans++;
        else break;
    }
    return ans;
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        add(i,a),add(i,b);
    }
    int re=hungary(m);
    cout<<re<<'\n';
    for(int i=1;i<=re;i++)cout<<to[i]<<'\n';
    return 0;
}

每架飞机需要一名英国飞行员和一名外籍飞行员,每一名外籍飞行员可以和多名英国飞行员配合,共有n名外籍飞行员,从1到n编号,m-n名英国飞行员,从m+1到n编号,输入a,b表示外籍飞行员a可以和英国飞行员b配合,求最大可以派出的飞机数量,以及方案,先外籍飞行员,后英国飞行员。外籍飞行员对应英国飞行员是一对多的关系,将外籍飞行员向英国飞行员连边,对外籍飞行员跑最大匹配,记录匹配答案。

    for(int i=1;i<=n;i++)if(to[i])cout<<i<<' '<<to[i]<<'\n';/*第一种输出方式*/
    for(int i=n+1;i<=m;i++)if(match[i])cout<<match[i]<<' '<<i<<'\n';/*第二种输出方式*/

n人,有的学生互相跳过舞,组建舞会,要求其中任何一对男生和女生都不能互相跳过舞,求最多参加多少个人。只考虑跳过舞的学生,每一对之间连无向边,因为性别未知,求出多少人不能参加舞会,即二分图的最大匹配,排除在最大匹配中的人,其他人一定能选,先对二分图进行任瑟,对同一性别的人跑匈牙利。

void color(int x,int c){
    col[x]=c;
    for(int i=h[x];i;i=e[i].next){
        int y=e[i].to;
        if(!col[y])color(y,3-c);
    }
}
inline int hungary(int n){
    int ans=0;
    for(int i=1;i<=n;i++){
        if(col[i]!=1)continue;
        ans+=dfs(i,i);
    }
    return ans;
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int a,b;cin>>a>>b;
        a++,b++;
        add(a,b);add(b,a);
    }
    for(int i=1;i<=n;i++)if(!col[i])color(i,1);
    cout<<n-hungary(n);
    return 0;
}

给定长n的数列a,b,m种操作(t,x,y),t=1表示可以使a[x]和a[y]都加一或都减一,t=2表示可以使a[x]-1,a[y]+1或a[x]+1,a[y]-1,任意顺序进行任意次操作,求是否存在方案使得a数列变成b数列。将每个位置看做点,先对于操作2连边,对于一个连通块,可以再总和不变下任意加减,用并查集缩成一个点。再对于操作1连边,若形成的图是二分图,则可以在左部点总和与右部点总和的差不变下任意加减,若不是二分图,则可以总和奇偶性不变的情况下任意加减。

#include<bits/stdc++.h>

using namespace std;

const int N=1e5+5;
int n,m,a[N],b[N],f[N],col[N],s[N],sum[3];
vector<int>e[N];
pair<int,int>p[N];
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
bool dfs(int x,int c){
    sum[col[x]=c]+=s[x];
    bool f=1;
    for(auto y:e[x]){
        if(col[y]==col[x])f=0;
        if(!col[y]&&!dfs(y,3-c))f=0;
    }
    return f;
}
inline bool solve(){
    int n,m,t=0;
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i],f[i]=i,s[i]=col[i]=0,e[i].clear();
    for(int i=1;i<=m;i++){
        int op,x,y;
        cin>>op>>x>>y;
        if(op==2)f[find(x)]=find(y);
        else p[++t]={x,y};
    }
    for(int i=1;i<=n;i++)s[find(i)]+=b[i]-a[i];
    for(int i=1;i<=t;i++){
        int x=find(p[i].first),y=find(p[i].second);
        e[x].push_back(y);
        e[y].push_back(x);
    }
    for(int i=1;i<=n;i++){
        if(find(i)!=i||col[i])continue;
        sum[1]=sum[2]=0;
        bool f=dfs(i,1);
        if(f&&sum[1]!=sum[2])return false;
        if(!f&&((sum[1]^sum[2])&1))return false;
    }
    return true;
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t;
    cin>>t;
    while(t--)cout<<(solve()?"YES\n":"NO\n");
    return 0;
}

给定一张二分图,判断是否其完美匹配数量恰好为1.结论,当所有点度数为2且有解时,解的数量一定大于1,即满足题意的情况一定会出现入读为1的点,顺着这条唯一的边找到对面一个点,这两个点是锁死在一起的,之后删掉这两个点及边,一直迭代下去,若可以全部删完,则符合题意,拓扑排序来寻找入读为1的点。

#include<bits/stdc++.h>

using namespace std;

const int N=2e6+6;
int n,m,tot,in[N];
bool del[N];
vector<int>v[N];
signed main(){
    int t;cin>>t;
    while(t--){
        cin>>n>>m;
        for(int i=1;i<=m;i++){
            int a,b;cin>>a>>b;b+=n;
            v[a].push_back(b);
            v[b].push_back(a);
            in[a]++;in[b]++;
        }
        queue<int>q;
        tot=0;
        for(int i=1;i<=(n<<1);i++)if(in[i]==1)q.push(i);/*入度为1的点入队*/
        while(!q.empty()){
            int x=q.front(),p=0;q.pop();
            if(del[x]||in[x]!=1)continue;/*左右两边一起迭代,已经入队的点的状态也可能改变,已经被删掉或入度不为1就跳过*/
            del[x]=1;/*删点*/
            tot++;/*累计数量*/
            while(del[v[x][p]])p++;/*找到仅存的那条边*/
            del[v[x][p]]=1;/*找到这条边另一边的点并删掉*/
            tot++;
            x=v[x][p];
            for(auto y:v[x])if(!del[y]&&(--in[y]==1))q.push(y);/*删边,当前点未被删且度数减小到1则入队*/
        }
        if(tot==(n<<1))cout<<"Renko\n";/*全删了*/
        else cout<<"Merry\n";
        for(int i=1;i<=(n<<1);i++)in[i]=del[i]=0,v[i].clear();
    }
    return 0;
}

n*n网格中有k个小行星,每次可以消除一行或一列的全部小行星,求消除所有小行星的最小代价。每个小行星可以通过本行或本列消除,二者至少选择一个,将坐标x,y看成一条边的两个端点,形成的图中任意一条边都至少有一个端点有武器,也就是二分图的最小点覆盖。单向连边行编号和列编号,再对前者跑匈牙利计算最大匹配即可。

	for(int i=1;i<=k;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
	}

n条线段或横或竖,同向线段之间互不相交,端点在另一条线端上也算相交,求最多选出几条线段使得被选线段互不相交。最大独立集问题,统计竖边的数量,有交点就连边,最后跑匈牙利。

    for(int i=1;i<=n;i++){
        int a,b,c,d;cin>>a>>b>>c>>d;
        if(a>c)swap(a,c);
        if(b>d)swap(b,d);
        if(a==c)q[i]=1,m++;/*竖向*/
        else q[i]=2;/*横向*/
        p[i]={a,b,c,d};
    }
    for(int i=1;i<n;i++)for(int j=i+1;j<=n;j++){
        if(q[i]==1/*前者竖向*/&&q[j]==2/*后者横向*/&&get<0>(p[j])<=get<0>(p[i])/*横者左端点在竖者左边*/&&get<0>(p[i])<=get<2>(p[j])/*竖者在横者右端点左边*/&&get<1>(p[i])<=get<1>(p[j])/*横者在竖者下端点上面*/&&get<1>(p[j])<=get<3>(p[i])/*横者在竖者上端点下边*/)add(i,j+m);
        if(q[i]==2/*前者横向*/&&q[j]==1/*后者竖向*/&&get<0>(p[i])<=get<0>(p[j])/*横者左端点在竖者左边*/&&get<0>(p[j])<=get<2>(p[i])/*竖者在横者右端点左边*/&&get<1>(p[j])<=get<1>(p[i])/*横者在竖者下端点上面*/&&get<1>(p[i])<=get<3>(p[j])/*横者在竖者上端点下边*/)add(j,i+m);
    }
    for(int i=1;i<=n;i++){
        if(q[i]==2)continue;
        ans+=dfs(i,i);
    }
    cout<<n-ans;

对于x,y属于0到n-1,定义其距离D(x,y)=min{|x-y|,n-|x-y|},现给出每个i和t[i]的距离d[i]=D(i,t[i]),要求构造一个字典序最小的序列t满足题目要求,t为长n且由0到n-1这n个数组成的序列。对于每一个i向t[i+d[i]],t[i-d[i]],t[i-n+d[i]],t[i+n-d[i]]连边,转化成求一种方案使得i对应一个它连向的t,二分图匹配,若无法完美匹配则无解。为了字典序最小,首先肯定要从小到大枚举他的儿子,若有没匹配的则直接匹配,否则去抢一个已经匹配的。这样保证了这个点字典序一定最优,但无法保证抢的点及增广路上其他店最优,为了保证序列前面的最优,并且因为每个左部点只会有两条边相连,所以可以倒着匹配来使字典序最小。

signed main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>d[i];
    for(int i=1;i<=n;i++){
        if(i+d[i]<=n)v[i].push_back(i+d[i]);
        if(i-d[i]>0)v[i].push_back(i-d[i]);
        if(i+n-d[i]<=n)v[i].push_back(i+n-d[i]);
        if(i-n+d[i]>0)v[i].push_back(i-n+d[i]);
        if(!v[i].empty())sort(v[i].begin(),v[i].end());
    }
    int ans=0;
    for(int i=n;i;i--)ans+=dfs(i,i);
    if(ans<n)return cout<<"No Answer",0;
    for(int i=1;i<=n;i++)cout<<to[i]-1<<' ';
    return 0;
}

对于一般的二分图完美匹配,求字典序最小。在开始时先对整张图跑一次二分图匹配,x与y匹配只会影响两个点,目前与x匹配的点与目前与y匹配的点,每次只用对影响的这两个点中的任意一个点跑一次增光路即可。

#include<bits/stdc++.h>

using namespace std;

const int N=1e4+4;
int n,m,match[N],vis[N],to[N],d[N];
vector<int>v[N];
bool exi[N<<1];
bool dfs(int x,int t){
    if(vis[x]==t||!exi[x])return false;
    vis[x]=t;
    for(auto y:v[x]){
        if(!exi[n+y])continue;
        if(!match[y]||dfs(match[y],t)){
            match[y]=x;
            to[x]=y;
            return true;
        }
    }
    return false;
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>d[i];
    for(int i=1;i<=n;i++){
        if(i+d[i]<=n)v[i].push_back(i+d[i]);
        if(i-d[i]>0)v[i].push_back(i-d[i]);
        if(i+n-d[i]<=n)v[i].push_back(i+n-d[i]);
        if(i-n+d[i]>0)v[i].push_back(i-n+d[i]);
        if(!v[i].empty())sort(v[i].begin(),v[i].end());
    }
    for(int i=1;i<=(n<<1);i++)exi[i]=1;
    int ans=0;
    for(int i=1;i<=n;i++){
        ans+=dfs(i,i);
    }
    if(ans<n)return cout<<"No Answer",0;
    memset(vis,0,sizeof(vis));
    int cnt=0;
    for(int x=1;x<=n;x++){
        for(auto y:v[x]){
            bool f=0;
            if(to[x]==y)f=1;
            else{
                exi[x]=exi[n+y]=0;
                match[to[x]]=0;
                if(dfs(match[y],++cnt))f=1;
                else match[to[x]]=x;
                exi[x]=exi[n+y]=1;
            }
            if(f){
                exi[x]=exi[n+y]=0;
                cout<<y-1<<' ';
                break;
            }
        }
    }
    return 0;
}

电脑给出棋盘上,最多能放几个bishop,国际象棋中一共有6种棋子:king(国王),queen(皇后),bishop(教主),knight(骑士),rook(车)pawn(步兵)。queen可以攻击同行同列同对角线;knight攻击类似中国象棋中的马;rook攻击水平和垂直两条线上的所有格子;pawn攻击前方两条斜线方向各一格;king攻击周围8个方向各1格;bishop攻击两条对角线上的所有格子。除knight以外,所有棋子的攻击范围均会被别的棋子所阻挡。(“前方”指x递增的方向,x行y列)

将坐标转45°之后建图,行列匹配,对于每个单位,在原有攻击范围上加一个bishop的攻击范围,二分图匹配。

#include<bits/stdc++.h>

using namespace std;

const int N=2e3+3;
int n,m,h[N<<4],tot,match[N<<4],vis[N<<4],p[N][2],wx[8]={0,-1,-1,-1,0,1,1,1},wy[8]={-1,-1,0,1,1,1,0,-1},kx[8]={-1,-2,-2,-1,1,2,2,1},ky[8]={-2,-1,1,2,2,1,-1,-2};
bool av[N][N],a[N],b[N];
char mp[N][N];
struct edge{
	int to,next;
}e[N*N];
inline void add(int x,int y){
	e[++tot]={y,h[x]};h[x]=tot;
}
bool dfs(int x,int t){
	for(int i=h[x];i;i=e[i].next){
		int y=e[i].to;
		if(vis[y]==t)continue;
		vis[y]=t;
		if(!match[y]||dfs(match[y],t)){
			match[y]=x;
			return true;
		}
	}
	return false;
}
inline void build(){
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(!av[i][j]){
		int x=p[i][0]+j-1,y=p[i][1]+j-1;
		if((!b[x])&&(!a[y]))add(x,y+n+m);
	}
}
inline void solveb(int x,int y){
	av[x][y]=1;
	b[p[x][0]+y-1]=a[p[x][1]+y-1]=1;
}
inline void solvek(int x,int y){
	solveb(x,y);
	for(int i=0;i<8;i++)av[x+wx[i]][y+wy[i]]=1;
}
inline void solveq(int x,int y){
	int qx[4]={0,-1,0,1},qy[4]={-1,0,1,0};
	solveb(x,y);
	for(int i=0;i<4;i++)for(int j=1;;j++){
		if(mp[x+qx[i]*j][y+qy[i]*j]!='.')break;
		av[x+qx[i]*j][y+qy[i]*j]=1;
	}
}
inline void solven(int x,int y){
	solveb(x,y);
	for(int i=0;i<8;i++)if(x+kx[i]>0&&y+ky[i]>0)av[x+kx[i]][y+ky[i]]=1;
}
inline int hungray(int n){
	int ans=0;
	for(int i=1;i<=n;i++)ans+=dfs(i,i);
	return ans;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>mp[i]+1;
	p[1][0]=1,p[1][1]=m;
	for(int i=2;i<=n;i++)p[i][0]=p[i-1][0]+1,p[i][1]=p[i-1][1]-1;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		if(mp[i][j]=='.')continue;
		switch(mp[i][j]){
			case 'K':solvek(i,j);break;
			case 'Q':solveq(i,j);break;
			case 'B':solveb(i,j);break;
			case 'R':solveq(i,j);break;
			case 'P':solveb(i,j);break;
			case 'N':solven(i,j);break;
		}
	}
	build();
	cout<<hungray(n+m);
	return 0;
}

nm的网格,为空地,x为软石,#为硬石,空地可放炸弹,炸弹不可穿硬石,求最多可以放的炸弹数量。硬石会把同一行和列分成两个不同的部分,记录每个点所属的行编号和列编号,遇到硬石就新建编号,最后连边行编号和列编号,二分图最大匹配。

signed main(){
	cin>>n>>m;
	bool f=1;/*第一次单独开一行*/
	pair<int,int>la;/*每个点的行编号由上一次转以来*/
	for(int i=1;i<=n;i++){
		cin>>s[i]+1;
		for(int j=1;j<=m;j++){
			if(s[i][j]=='#'){/*下次新开一行*/
				f=1;
				continue;
			}
			if(s[i][j]=='*'){
				row[i][j]=row[la.first][la.second]+f;/*如果是新开一行则编号+1*/
				cn+=f;/*更新编号数量*/
				la={i,j};
				f=f?0:f;/*如果这次是新开的一行那么f清零*/
			}
		}
		f=1;/*换行+1*/
	}
	f=1;/*第一次单独开一列*/
	la={0,0};
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			if(s[j][i]=='#'){/*注意i与j取反*/
				f=1;
				continue;
			}
			if(s[j][i]=='*'){
				col[j][i]=col[la.second][la.first]+f;/*列的编号*/
				cm+=f;
				la={i,j};
				f=f?0:f;
			}
		}
		f=1;
	}
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='*')add(row[i][j],col[i][j]);/*行编号向列编号连边*/
	cout<<hungray(cn);/*为每个行编号跑匈牙利*/
	return 0;
}

给定01矩阵,0可以放攻击装置,按照“日”字进行攻击,求在最多放多少个装置,使得他们不互相攻击。看做黑白棋盘可以发现,每个点只能攻击与自己异色的点,在读入时向已经读入的点进行连边,二分图最大独立集,最后给每个是0且颜色相同的点跑匈牙利。注意若a可以攻击b,那么b也可以攻击a,所以要建双向边。

int dx[4]={1,2,2,1},dy[4]={2,1,-1,-2};
signed main(){
	cin>>n;
	static int cnt,sum,ans;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
		scanf("%1d",&mp[i][j]);
		num[i][j]=++cnt;sum+=mp[i][j]^1;
		if(!mp[i][j])for(int k=0;k<4;k++)
		if(i>dx[k]&&j>dy[k]&&j-dy[k]<=n&&!mp[i-dx[k]][j-dy[k]]){
			add(num[i][j],num[i-dx[k]][j-dy[k]]);
			add(num[i-dx[k]][j-dy[k]],num[i][j]);
		}
	}
	cnt=0;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if((i+j&1)&&!mp[i][j])ans+=dfs(num[i][j],++cnt);
	cout<<sum-ans;
	return 0;
}

若能将无向图G画在平面上使得任意两条无重合顶点的边不想交则成G为平面图,现要求判断途中存在哈密顿回路的图是否是无向图。将哈密顿回路画成一个圆,原图中不在回路上的边可以看作是园的弦,圆中两条相交的弦,可以将一条翻到圆外,若都翻到圆外则也会相交,将弦视为点,相交的弦连边,之后判断是否是二分图。注意平面图有一条性质,m<=3*n-6.

#include<bits/stdc++.h>

using namespace std;

const int N =1e4+4;
int n,m,h[N],tot,col[N],from[N],to[N],g[N];
struct edge{
	int to,next;
}e[(int)1e6];
inline void add(int x,int y){
	e[++tot]={y,h[x]};h[x]=tot;
}
bool dfs(int x,int c){
	col[x]=c;
	for(int i=h[x];i;i=e[i].next){
		int y=e[i].to;
		if(col[x]==col[y])return false;
		if(!col[y]&&!dfs(y,-c))return false;
	}
	return true;
}
inline bool check(int a,int b,int c,int d){
	return (a<c&&c<b&&b<d)||(c<a&&a<d&&d<b);
}
signed main(){
	int t;cin>>t;
	while(t--){
		cin>>n>>m;
		memset(h,0,sizeof(h));
		memset(col,0,sizeof(col));
		memset(g,0,sizeof(g));
		for(int i=1;i<=m;i++)cin>>from[i]>>to[i];
		for(int i=1;i<=n;i++){
			int x;cin>>x;
			g[x]=i;
		}
		if(m>3*n-6){
			cout<<"NO\n";
			continue;
		}
		for(int i=1;i<m;i++)for(int j=i+1;j<=m;j++){
			int a=g[from[i]],b=g[to[i]],c=g[from[j]],d=g[to[j]];
			if(a>b)swap(a,b);
			if(c>d)swap(c,d);
			if(check(a,b,c,d))add(i,j),add(j,i);
		}
		for(int i=1;i<=m;i++){
			if(col[i])continue;
			if(!dfs(i,1)){
				cout<<"NO\n";
				goto a;
			}
		}
		cout<<"YES\n";
		a:if(1);
	}
	return 0;
}

给定n*m的矩阵,从中选出n个数,任意两个数不能在同一行或同一列,求其中第k大的数的最小值。二分一个第k大的数,小于等于这个数的行向列连边,跑最大匹配,判断能不能取出n-k+1个<=mid的数,

inline void build(int k){
	tot=0;
	memset(h,0,sizeof(h));
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(a[i][j]<=k)add(i,j);
}
signed main(){
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>a[i][j];
	int l=1,r=1e9,ans=0;
	while(l<=r){
		int mid=l+r>>1;
		build(mid);
		if(hungary(n)>=n-k+1)ans=mid,r=mid-1;
		else l=mid+1;
	}
	cout<<ans;
}

abc的立方体,每次对尺寸xyz的区域消毒需要使用min(x,y,z)个单位试剂,求全部消毒最少要用多少个单位试剂。对于二维空间,消除一个xy的矩形需要用min(x,y)的代价,这里用min(x,y)条竖线或横线就可以覆盖xy的矩形,也就是二分图的最小点覆盖。abc<=5000,所以min(a,b,c)<=17,钦定是a<=17,暴力枚举1到a中每一层是直接削掉还是稍后处理,对于稍后处理的层,剥离出来,拍扁成二维问题求解。

signed main(){
	int tt;cin>>tt;
	while(tt--){
		int ans=0x7fffffff,a,b,c,mi,cnt=0;
		cin>>a>>b>>c;
		mi=min({a,b,c});
		for(int i=1;i<=a;i++)for(int j=1;j<=b;j++)for(int k=1;k<=c;k++){
			int x;cin>>x;
			if(!x)continue;
			s[1][++cnt]=i;
			s[2][cnt]=j;
			s[3][cnt]=k;
		}
		if(mi==b)swap(a,b),swap(s[1],s[2]);/*钦定a最小*/
		else if(mi==c)swap(a,c),swap(s[1],s[3]);
		for(int t=0;t<(1<<a);t++){
			for(int i=1;i<=b;i++)h[i]=0;
			for(int i=1;i<=c;i++)match[i]=vis[i]=0;
			int re=tot=0;
			for(int i=1;i<=a;i++)if(t&(1<<i-1))/*这一层直接削掉,代价min(a=1,b,c)=1*/q[i]=0,re++;else q[i]=1;/*这一层稍后处理*/
			for(int i=1;i<=cnt;i++)if(q[s[1][i]])add(s[2][i],s[3][i]);
			for(int i=1;i<=b;i++)re+=dfs(i,i);
			ans=min(ans,re);
		}
		cout<<ans<<'\n';
	}
}

给定带权有向图,选出n+1条链,问能否全部点覆盖,若不能,求不能覆盖的点权最小值最大是多少。若问全部覆盖,floyed传递闭包后二分图最大匹配即可,若不能完全覆盖,则二分答案,看是否可以覆盖掉比mid值小的点。

inline bool check(int x){
	int cnt=0;
	for(int i=1;i<=n;i++)if(a[i]<x)cnt++;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)g[i][j]=(a[i]<x&&a[j]<x)?w[i][j]:0;
	memset(match,0,sizeof(match));
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)if(a[i]<x)cnt-=dfs(i,i);
	return cnt<=m+1;
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>m>>n;
	for(int i=1,x,y;i<=n;i++){
		cin>>a[i]>>x;b[i]=a[i];
        for(int j=1;j<=x;j++)cin>>y,w[i][y]=1;
	}
	for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)w[i][j]|=w[i][k]&w[k][j];
	sort(b+1,b+1+n);
	int tot=unique(b+1,b+1+n)-b-1;
	for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+1+tot,a[i])-b;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)g[i][j]=w[i][j];
	if(check(tot+1))return cout<<"AK",0;
	int l=1,r=tot,ans;
	while(l<=r){
		int mid=l+r+1>>1;
		if(check(mid))l=mid+1,ans=mid;
		else r=mid-1;
	}
	cout<<b[ans];
	return 0;
}
posted @ 2022-11-14 17:42  半步蒟蒻  阅读(129)  评论(0)    收藏  举报