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;elsec[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;
}
费用流
时间复杂度Θ(nmf)\Theta(nmf)Θ(nmf)
此题满流,边级别是n2n^2n2,于是费用流时间复杂度Θ(n4)\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))
-
顶标:两边点都有的标记(左xix_ixi右yiy_iyi)满足xi+yi≥wi,jx_i+y_i\ge w_{i,j}xi+yi≥wi,j,不唯一
-
相等边:xi+yi=wi,jx_i+y_i=w_{i,j}xi+yi=wi,j的边 相等子图:相等边构成的子图。
-
交错树:增广路径形成的树。
-
KM算法核心定理 :对于某组可行顶标,如果其相等子图存在完美匹配,那么,该匹配就是原二分图的最大权完美匹配。
dfs时间复杂度O(n4)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(n3)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;elsec[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;
}
要加油哦~
板子还是记住吧,具体啥的也没啥