C++题解:P1894 [USACO4.2]完美的牛栏The Perfect Stall —— 求二分图的最大匹配算法其一:匈牙利算法 (增广路,匹配,最大匹配)

在看这道题之前,我们先来了解一下什么是二分图及与二分图匹配的相关概念及基础知识。

基础知识

故名思义,二分图本质上还是由点和边构成的数据结构,与之不同的是,二分图相当于把一张图分成了两个部分,也就是两个部。部与部之间的点没有边相连,以下的几个图都可以算作二分图:
C++题解:P1894 [USACO4.2]完美的牛栏The Perfect Stall —— 求二分图的最大匹配算法其一:匈牙利算法 (增广路,匹配,最大匹配)_第1张图片C++题解:P1894 [USACO4.2]完美的牛栏The Perfect Stall —— 求二分图的最大匹配算法其一:匈牙利算法 (增广路,匹配,最大匹配)_第2张图片(图中的箭头无意义,并不代表有向边)
现在来简单说一下匹配和最大匹配(因为只涉及匈牙利算法,所以不讲其他杂七杂八的东西。最小点覆盖什么的以后再讲)
简单来说匹配就是从二分图中选出一些边,这些边没有共同的顶点。这些边组成的边集就叫做这个二分图中的一个边集。下面是一个例子,红色边便是匹配:
C++题解:P1894 [USACO4.2]完美的牛栏The Perfect Stall —— 求二分图的最大匹配算法其一:匈牙利算法 (增广路,匹配,最大匹配)_第3张图片
最大匹配就是匹配中边数最大的一个匹配。如图为上图的一个最大匹配(注意!最大匹配并不是唯一的)
C++题解:P1894 [USACO4.2]完美的牛栏The Perfect Stall —— 求二分图的最大匹配算法其一:匈牙利算法 (增广路,匹配,最大匹配)_第4张图片
关于匈牙利算法,还有一个重要的概念,便是增广路。增广路在各种算法中都有很大的作用。这里只讲解一下其最基本的一些概念。
增广路的意义便是选取一个不在任何匹配中的一条边作为起点,再选取一条与它相邻的匹配中的一条边,下下一条边又是一条与下一条边相邻的边的不在匹配中的一条边。连在一起交错进行终点不为匹配中的边便是一条增广路。(相信你已经看懵了,下面便给出流程来)
C++题解:P1894 [USACO4.2]完美的牛栏The Perfect Stall —— 求二分图的最大匹配算法其一:匈牙利算法 (增广路,匹配,最大匹配)_第5张图片这是一个二分图的匹配,
从任意一个顶点开始进行增广路后:
C++题解:P1894 [USACO4.2]完美的牛栏The Perfect Stall —— 求二分图的最大匹配算法其一:匈牙利算法 (增广路,匹配,最大匹配)_第6张图片
(x2,y1)不在匹配中,作为起点。所以整个增广路就为:(x2,y1)-(y1,x1)-(x1,y3)-(y3,x4)-(x4,y2)。
绿色边为不在匹配中的边。现在我们将绿色边转为红色边,红色边转为绿色边:
C++题解:P1894 [USACO4.2]完美的牛栏The Perfect Stall —— 求二分图的最大匹配算法其一:匈牙利算法 (增广路,匹配,最大匹配)_第7张图片
发现形成了一个新的匹配。发现无法再找到新的增广路,那么这些红色边便是最大匹配。
在我们程序的实现中,无法找到增广路后,当前匹配便是最大匹配。

算法部分

先给出主要的代码:

bool dfs(int u) {//dfs用来判断从当前节点出发有没有新的可覆盖的点
    for(int i=0,v;i

附:各种数论模板:

卡常必备!快速读入

int read() {
    int s=0,f=1;char a=getchar();
    while(a<'0' || a>'9') { if(a=='-') f=-1; a=getchar(); }
    while(a>='0' && a<='9') { s=s*10+a-'0'; a=getchar() ; }
    return f*s; 
}

数论基础:扩展欧几里得算法(解二元一次不等式及求最大公因数)

int exgcd (int a,int b ,int& x,int& y) {//函数将返回(gcd(a,b))
    if(b==0) {
        x=1,y=0;
        return a;
    }    
    int sum=exgcd(b,a%b,y,x)
    y-=(a/b)*x;
    return sum;

求逆元(扩展欧几里得法)

int exgcd (int a,int b ,int& x,int& y) {//函数将返回(gcd(a,b))
    if(b==0) {
        x=1,y=0;
        return a;
    }    
    int sum=exgcd(b,a%b,y,x)
    y-=(a/b)*x;
    return sum;
}


int inv(int sum,int mod) {//注意:sum与mod必须互质
    int x,y;
    exgcd(sum,mod,x,y);
    return (x%mod+mod)%mod;
}

快速幂(求逆元的费马小定理方法用得到)

LL qkpow(LL base,LL indexx)//MOD为模数,题目不要去去掉即可
{
    LL sum=1;
    while(indexx>0)
    {
        if(indexx&1)
            sum=sum*a%MOD;
        base=base*base%MOD;
        indexx>>=1;
    }
    return sum%MOD;
}

费马小定理求逆元(MOD要为素数)

LL qkpow(LL base,LL indexx,LL MOD){//改了一下
    LL sum=1;
    while(indexx>0)
    {
        if(indexx&1)
            sum=sum*base%MOD;
        base=base*base%MOD;
        indexx>>=1;
    }
    return sum%MOD;
}

LL inv(LL sum,LL mod) {
    return qkpow(sum,mod-2,mod);
}

线性递推法求逆元(适用于数据较集中,但模数要为素数)
给一下同校大佬的证明方法

    rfac[0]=rfac[1]=1;
    for(int i=2;i<=n;i++)
        rfac[i]=1ll*rfac[MOD%i]*(P-P/i)%P;
}   
//rfac[i]为i的逆元,MOD必须为质数

补充!预处理阶乘的逆元来快速求组合数:

void prepare() {
   fac[0]=fac[1]=rfac[0]=rfac[1]=1;
   for(int i=2;i<=n;i++) {
       fac[i]=1ll*fac[i]*fac[i-1]%MOD;
       rfac[i]=1ll*rfac[P%i]*(P-P/i)%P;
   }
   for(int i=2;i<=n;i++)
       rfac[i]=1ll*rfac[i]*rfac[i-1]%P;
}

int C(int n,int m) {
   return 1ll*fac[n]*rfac[m]%P*rfac[n-m]%P;
}

既然讲到了组合数,那就再多讲一点

Lucas定理模板!
Lucas定理:

int Lucas(int n,int m) {
    if(m==0 || m==n) return 1;
    return 1ll*C(n%MOD,m%MOD)*Lucas(n/MOD,m/MOD)%MOD;
}

补充一个定理,具体实现请看我的博客:

https://blog.csdn.net/weixin_44049566/article/details/87914975#comments

下面只是组合数的用法,重点是上半部分。

下面有请重点嘉宾:
CRTpro(扩展中国剩余定理)

#include 
#include 
#include 
#include 
#include 
using namespace std;
 
#define N 100000001
#define LL long long

LL exgcd(LL a,LL b,LL &x,LL &y){
    if(b==0){
		x=1,y=0;
		return a;
	}
    LL sum=exgcd(b,a%b,y,x);
    y-=(a/b)*x;
    return sum;
}

LL T,m[N],a[N];

int main() {
    while(cin>>T) {
    	LL m1,r1,flag=0;
    	cin>>m1>>r1;
		T--;
		while(T--) {
			LL x,y,d,m2,r2,tx,sum;
			cin>>m2>>r2;
			sum=r2-r1;
			d=exgcd(m1,m2,x,y);
			if(sum%d) flag=1;
			x*=sum/d;
			tx=(x%(m2/d)+m2/d)%(m2/d);
			r1=m1*tx+r1;
			m1=m1/d*m2;
		}
		if(flag) {	cout<<-1<

具体原理请看我的博客,自以为讲的很详细:

https://blog.csdn.net/weixin_44049566/article/details/88841843

另一个重点内容:欧拉函数(顺便找出了n以内的素数)

void Euler(int n) {
	phi[1]=1;
	for(int i=2;i<=n;i++) {
		if(vis[i]==0)  {
			phi[i]=i-1;
			prime[++cnt]=i;
		}
		for(int j=1;j<=cnt && i*prime[j]<=n;j++) {
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)  {
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			else phi[i*prime[j]]=phi[i]*phi[prime[j]];
		}
	}	
}

上面的适用于数据较小且集中的题,范围大用这个

int phi(int x) {
	int p=x;
	for(int i=2;i*i<=x;i++) {
		if(x%i==0) {
			p-=p/i;
			while(x%i==0)
				x/=i;
		}
	}
	if(x>1) p-=p/x;
	return p;
}

矩阵乘法

struct Matrix {
    LL n,m,c[N][N];
    Matrix() { memset(c,0,sizeof(c)); };
    void _read() {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                scanf("%lld",&c[i][j]);
    }
    Matrix operator * (const Matrix& a) {
        Matrix r;
        r.n=n;r.m=a.m;
        for(int i=1;i<=r.n;i++)
            for(int j=1;j<=r.m;j++)
                for(int k=1;k<=m;k++)
                    r.c[i][j]= (r.c[i][j]+ (c[i][k] * a.c[k][j])%mod)%mod;
        return r;
    }
    void _print() {
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=m;j++) {
                if(j!=1) cout<<" ";
                cout<0) {
            if(indexx&1) sum=sum*tmp;
            tmp=tmp*tmp;
            indexx/=2;
        }
        return sum;
    }
}

你可能感兴趣的:(C++,数论,二分图及匹配)