题目
思路
把时间和物品进行二分图最大匹配,因为答案求的最小值,所以是负值情况下的最大值
代码
BFS O ( n 3 ) \text{BFS}O(n^3) BFSO(n3)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 310;
int x,y,z,v;
int w[N][N];//边权
int la[N], lb[N];//左、右部点的顶标
bool va[N], vb[N];//访问标记,是否在交错树中
int match[N];//右部点匹配的左部点(一个只能匹配一个嘛)
int n;
int delta, upd[N];
int p[N];
int c[N];
void bfs(int x)
{
int a, y = 0, y1 = 0;
for(int i = 1; i <= n; ++ i)
p[i] = 0, c[i] = INF;
match[y] = x;
do{
a = match[y], delta = INF, vb[y] = true;
for(int b = 1; b <= n; ++ b){
if(!vb[b]){
if(c[b] > la[a] + lb[b] - w[a][b])
c[b] = la[a] + lb[b] - w[a][b], p[b] = y;
if(c[b] < delta)//差值还是取最小的
delta = c[b], y1 = b;
}
}
for(int b = 0; b <= n; ++ b)
if(vb[b])
la[match[b]] -= delta, lb[b] += delta;
else c[b] -= delta;
y = y1;
}while(match[y]);
while(y) match[y] = match[p[y]], y = p[y];
}
int KM()
{
for(int i = 1; i <= n; ++ i)
match[i] = la[i] = lb[i] = 0;
for(int i = 1; i <= n; ++ i){
for(int j = 1; j <= n; ++ j)
vb[j] = false;
bfs(i);
}
int res = 0;
for(int y = 1; y <= n; ++ y)//若匹配失败w[match[y]][y]=INF;
if(w[match[y]][y]!=INF)
res += w[match[y]][y];
return res;
}
int d(int x,int y,int z,int v,int t){
return x*x+y*y+(z+v*t)*(z+v*t);
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>x>>y>>z>>v;
for(int j=1;j<=n;j++){
w[i][j]=-d(x,y,z,v,j-1);
// cout<<x[i]<<" "<<y[i]<<" "<<z[i]<<" "<<love[i][j]<<endl;
}
}
cout<<-1*KM()<<endl;
return 0;
}
DFS O ( n 4 ) 板 子 \text{DFS}O(n^4)板子 DFSO(n4)板子
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define IO ios::sync_with_stdio(false)
typedef long long ll;
using namespace std;
const int INF = 0x3f3f3f3f;
const int mn=305;
const ll mod=1000000007;
int match[mn],lx[mn],ly[mn],slack[mn],fa[mn*3];
int G[mn][mn];
bool visx[mn],visy[mn];
int n,nx,ny;
int findpath(int x)
{
int tempDelta;
visx[x]=true;
for(int y=1;y<=ny;y++){
if(visy[y])continue;
tempDelta =lx[x]+ly[y]-G[x][y];
if(tempDelta == 0){
visy[y] = true;
fa[y+nx]=x;
if(match[y] == -1){
return y+nx;
}
fa[match[y]]=y+nx;//记录交替树的父亲信息(为了区别X,Y集合,Y的点都映射成n+y)
int res=findpath(match[y]);
if(res>0)return res;//返回增广路的末端叶子节点
}
else if(slack[x] > tempDelta)//统计以x为准的slack值。
slack[x] = tempDelta;
}
return -1;
}
void KM()
{
for(int x = 1 ; x <= nx ; ++x){
for(int i = 1 ; i <= nx ; ++i) slack[i] =INF;
for(int i=1;i<=nx+ny;i++)fa[i]=-1;
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));//换到外面,可以保留原树
int fir=1;int leaf=-1;
while(true){
if(fir==1){
leaf=findpath(x);
fir=0;
}
else{
for(int i=1;i<=nx;i++){
if(slack[i]==0){//只接着搜有新边加入的X点
slack[i]=INF;//slack要重新清空,方以后接着用
leaf=findpath(i);
if(leaf>0)break;
}
}
}
if(leaf>0){
int p=leaf;
while(p>0){
match[p-nx]=fa[p];
p=fa[fa[p]];//顺着记录一路找找上去
}
break;
}
else{
int delta =INF;
for(int i = 1 ; i <= nx ; ++i)
if(visx[i] && delta > slack[i])
delta = slack[i];
for(int i = 1 ; i <= nx ; ++i)
if(visx[i]) {lx[i] -= delta;slack[i]-=delta;}//X点的slack要响应改变,slack变0说明有新边加入
for(int j = 1 ; j <= ny ; ++j){
if(visy[j])
ly[j] += delta;
}
}
}
}
}
int solve()
{ //初始化:
memset(match,-1,sizeof(match));
memset(ly,0,sizeof(ly));
memset(fa,0,sizeof(fa));
for(int i = 1 ; i <= nx ; ++i){
lx[i] = -1;
for(int j = 1 ; j <= ny ; ++j)
if(lx[i] < G[i][j])
lx[i] = G[i][j];
}
KM();
int ans=0;
for(int i=1;i<=ny;++i)
{
if(match[i]!=-1) ans+=G[match[i]][i];
}
return ans;
}
int main()
{
int n;
while(~scanf("%d",&n))
{
nx=n;ny=n;//nx左部图点数,ny右部图点数
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&G[i][j]);
}
}
int ans=solve();
printf("%d\n",ans);
}
return 0;
}