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+yi≥wi,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;
}
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;
}
要加油哦~
板子还是记住吧,具体啥的也没啥