J. Spy(多重匹配KM算法)

J. Spy

随机打,最后答案乘n,因为我方等概率的遇见敌人,相当于与n个敌人都打一遍,然后贡献累加作为匹配边权

bfs版本的KM
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=510;
ll w[N][N],lx[N],ly[N];//顶标
bool visx[N],visy[N];//记录是否在交错树上
int match[N];//匹配
int n,p[N];
ll delta,c[N];//delta和更新后的delta
void bfs(int x)
{
    int a,y=0,y1=0;
    for(int i=1;i<=n;i++) p[i]=0,c[i]=1e18;
    match[y]=x;
    do{
        a=match[y],delta=1e18,visy[y]=1;
        for(int b=1;b<=n;b++)
            if(!visy[b])
            {
                if(c[b]>lx[a]+ly[b]-w[a][b])
                    c[b]=lx[a]+ly[b]-w[a][b],p[b]=y;
                if(c[b]<delta)//Δ还是取最小的
                    delta=c[b],y1=b;
            }
        for(int b=0;b<=n;b++)
            if(visy[b])
                lx[match[b]]-=delta,ly[b]+=delta;
            else
                c[b]-=delta;
        y=y1;
    }while(match[y]);
    while(y) match[y]=match[p[y]],y=p[y];
}
ll KM()
{
    for(int i=1;i<=n;i++)
        match[i]=lx[i]=ly[i]=0;
    for(int i=1;i<=n;i++)
    {
        memset(visy,0,sizeof visy);
        bfs(i);
    }
    ll res=0;
    for(int i=1;i<=n;i++)
        res+=w[match[i]][i];
    return res;
}
ll A[N],P[N],B[N],C[N];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%lld",&A[i]);
    for(int i=1;i<=n;i++) scanf("%lld",&P[i]);
    for(int i=1;i<=n;i++) scanf("%lld",&B[i]);
    for(int i=1;i<=n;i++) scanf("%lld",&C[i]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            ll sum=0;
            for(int k=1;k<=n;k++)
                if(A[k]<B[i]+C[j])
                    sum+=P[k];
            w[i][j]=sum;
        }
    printf("%lld\n",KM());
    return 0;
}
费用流

时间复杂度 Θ ( n m f ) \Theta(nmf) Θ(nmf)
此题满流,边级别是 n 2 n^2 n2,于是费用流时间复杂度 Θ ( n 4 ) \Theta(n^4) Θ(n4)
卡费用流就离谱,不得不多看一个KM算法www

#define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0)
#pragma GCC optimize(2)
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N=810,M=330010,INF=0x3f3f3f3f;
int n,S,T;
int h[N],e[M],ne[M],w[M],f[M],idx;
int d[N],flow[N],pre[N];
bool st[N];
queue<int> q;
void add(int a,int b,int c,int d)
{
    e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;
    e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
}
bool spfa()
{
    memset(d,-0x3f,sizeof d);
    memset(flow,0,sizeof flow);
    d[S]=0,flow[S]=INF;
    q.push(S);
    while(q.size())
    {
        int t=q.front();q.pop();
        st[t]=0;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(f[i]&&d[j]<d[t]+w[i])
            {
                d[j]=d[t]+w[i];
                pre[j]=i;
                flow[j]=min(f[i],flow[t]);
                if(!st[j])
                {
                    q.push(j);
                    st[j]=1;
                }
            }
        }
    }
    return flow[T];
}
int EK()
{
    int cost=0;
    while(spfa())
    {
        int t=flow[T];
        cost+=d[T]*t;
        for(int i=T;i!=S;i=e[pre[i]^1])
        {
            f[pre[i]]-=t;
            f[pre[i]^1]+=t;
        }
    }
    return cost;
}
ll A[N],P[N],B[N],C[N];
int main()
{
    IO;
    cin>>n;
    S=0,T=2*n+1;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++) cin>>A[i];
    for(int i=1;i<=n;i++) cin>>P[i];
    for(int i=1;i<=n;i++) cin>>B[i];
    for(int i=1;i<=n;i++) cin>>C[i];
    for(int i=1;i<=n;i++) 
        add(S,i,1,0),add(i+n,T,1,0);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            int sum=0;
            for(int k=1;k<=n;k++)
                if(A[k]<B[i]+C[j])
                    sum+=P[k];
            if(sum) add(i,j+n,INF,sum);
        }
    cout<<EK()<<'\n';
    return 0;
}

KM算法

P6577 【模板】二分图最大权完美匹配
KM算法——二分图最大带权匹配
【算法笔记】二分图最大权匹配 - KM算法(dfs版O(n4) + bfs版O(n3))

  • 顶标:两边点都有的标记(左 x i x_i xi y i y_i yi)满足 x i + y i ≥ w i , j x_i+y_i\ge w_{i,j} xi+yiwi,j,不唯一

  • 相等边: x i + y i = w i , j x_i+y_i=w_{i,j} xi+yi=wi,j的边 相等子图:相等边构成的子图。

  • 交错树:增广路径形成的树。

  • KM算法核心定理 :对于某组可行顶标,如果其相等子图存在完美匹配,那么,该匹配就是原二分图的最大权完美匹配。

dfs时间复杂度 O ( n 4 ) O(n^4) O(n4)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=510;
ll w[N][N],lx[N],ly[N];//顶标
bool visx[N],visy[N];//记录是否在交错树上
int match[N];//匹配
int n,m;
ll delta;//delta和更新后的delta
bool dfs(int x)
{
    visx[x]=1;
    for(int y=1;y<=n;y++)
        if(!visy[y])
        {
            if(lx[x]+ly[y]==w[x][y])
            {
                visy[y]=1;
                if(!match[y]||dfs(match[y])) 
                    return match[y]=x,1;
            }
            else delta=min(delta,lx[x]+ly[y]-w[x][y]);// visx[x]=1,visy[y]=0更新delta
        }
    return 0;
}
ll KM()
{
    memset(lx,-0x3f,sizeof lx);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            lx[i]=max(lx[i],w[i][j]);
    for(int i=1;i<=n;i++)
        while(1)
        {
            memset(visx,0,sizeof visx);
            memset(visy,0,sizeof visy);
            delta=1e18;
            if(dfs(i)) break;
            for(int j=1;j<=n;j++)
            {//把所有在交错树上的左右部点都左加右减 Δ
                if(visx[j]) lx[j]-=delta;
                if(visy[j]) ly[j]+=delta;
            }
        }
    ll res=0;
    for(int i=1;i<=n;i++)
        res+=w[match[i]][i];
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) 
            w[i][j]=-1e18;
    for(int i=1;i<=m;i++)
    {
        int a,b;ll c;
        scanf("%d%d%lld",&a,&b,&c);
        w[a][b]=max(w[a][b],c);
    }
    printf("%lld\n",KM());
    for(int i=1;i<=n;i++)
        printf("%d ",match[i]);
    puts("");
    return 0;
}

KM算法详解和其更快优化方法(吊打传统的slack优化)

bfs 时间复杂度 O ( n 3 ) O(n^3) O(n3)

把DFS换成 BFS。本质上没有什么区别
但是每个左边的点只会进队、搜索一次。
用p数组记录的是增广交错树。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=510;
ll w[N][N],lx[N],ly[N];//顶标
bool visx[N],visy[N];//记录是否在交错树上
int match[N];//匹配
int n,m,p[N];
ll delta,c[N];//delta和更新后的delta
void bfs(int x)
{
    int a,y=0,y1=0;
    for(int i=1;i<=n;i++) p[i]=0,c[i]=1e18;
    match[y]=x;
    do{
        a=match[y],delta=1e18,visy[y]=1;
        for(int b=1;b<=n;b++)
            if(!visy[b])
            {
                if(c[b]>lx[a]+ly[b]-w[a][b])
                    c[b]=lx[a]+ly[b]-w[a][b],p[b]=y;
                if(c[b]<delta)//Δ还是取最小的
                    delta=c[b],y1=b;
            }
        for(int b=0;b<=n;b++)
            if(visy[b])
                lx[match[b]]-=delta,ly[b]+=delta;
            else
                c[b]-=delta;
        y=y1;
    }while(match[y]);
    while(y) match[y]=match[p[y]],y=p[y];
}
ll KM()
{
    for(int i=1;i<=n;i++)
        match[i]=lx[i]=ly[i]=0;
    for(int i=1;i<=n;i++)
    {
        memset(visy,0,sizeof visy);
        bfs(i);
    }
    
    ll res=0;
    for(int i=1;i<=n;i++)
        res+=w[match[i]][i];
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) 
            w[i][j]=-1e18;
    for(int i=1;i<=m;i++)
    {
        int a,b;ll c;
        scanf("%d%d%lld",&a,&b,&c);
        w[a][b]=max(w[a][b],c);
    }
    printf("%lld\n",KM());
    for(int i=1;i<=n;i++)
        printf("%d ",match[i]);
    puts("");
    return 0;
}

要加油哦~
板子还是记住吧,具体啥的也没啥

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值