[费用流] HDU 6767 New Equipments
2020 Multi-University Training Contest 2 (1005)
题目大意
Hall 定理
设二部图\(G=\langle V_1,V_2,E\rangle\)中,\(|V_1|\leq|V_2|\). \(G\) 中存在从 \(V_1\) 到 \(V_2\) 的完备匹配当且仅当 \(V_1\) 中任意 \(k\) 个顶点至少与 \(V_2\) 中的 \(k\) 个顶点相邻 \((k=1,2,\dots,|V_1|)\).
设二部图 \(G=\langle V_1,V_2,E\rangle\) 中, 如果存在 \(t\geq 1\), 使得 \(V_1\) 中每个顶点至少关联 \(t\) 条边, 而 \(V_2\) 中每个顶点至多关联 \(t\) 条边,则 \(G\) 中存在 \(V_1\) 到 \(V_2\) 的完备匹配。
题解
对于这道题,因为最多只有50个开口向上的二次函数,我们可以在每个二次函数的对称轴附近选取50个点,然后每个代表二次函数的点向每个代表 \(x\) 值的点连边,边权是二次函数在 \(x\) 处对应的函数值。由 Hall 定理可知,该二分图一定存在完备匹配。建完图后直接跑一遍费用流求出最小费用即可。
这道题比赛时想到了正解没写出来,太可惜了。一是没有开long long,二是他要求匹配数分别为 \(1\sim n\) 的最小代价。注意到每次找到的增广路增广的流量至多为1,只要每增广一次,记录一下答案即可,而不用跑 \(n\) 次费用流。
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;
#define RG register int
#define LL long long
const LL INF=1LL<<60;
struct edge{int next,to,c;LL w;};
edge G[1000010];
int head[3010];
LL Dis[3010];
int Pre[3010];
int incf[3010];
bool inQ[3010];
LL a[60],b[60],c[60];
int N,M,S,T,cnt=2,MaxFlow=0;
LL MinCost=0;
template<typename elemType>
inline void Read(elemType &T){
elemType X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
T=(w?-X:X);
}
inline void add_edge(int u,int v,int c,LL w){
G[cnt].c=c;
G[cnt].w=w;
G[cnt].to=v;
G[cnt].next=head[u];
head[u]=cnt++;
return;
}
bool SPFA(){
queue<int> Q;
memset(Dis,0x3f,sizeof(Dis));
memset(inQ,0,sizeof(inQ));
Q.push(S);Dis[S]=0;incf[S]=1<<29;inQ[S]=true;
while(!Q.empty()){
int now=Q.front();Q.pop();inQ[now]=false;
for(int i=head[now];i;i=G[i].next){
if(!G[i].c) continue;
if(Dis[G[i].to]>Dis[now]+G[i].w){
Dis[G[i].to]=Dis[now]+G[i].w;
incf[G[i].to]=min(incf[now],G[i].c);
Pre[G[i].to]=i;
if(!inQ[G[i].to]){
Q.push(G[i].to);
inQ[G[i].to]=true;
}
}
}
}
if(Dis[T]>INF) return false;
return true;
}
inline void Update(){
int u=T;
while(u!=S){
int i=Pre[u];
G[i].c-=incf[T];
G[i^1].c+=incf[T];
u=G[i^1].to;
}
MaxFlow+=incf[T];
MinCost+=Dis[T]*incf[T];
return;
}
vector<int> Range;
int Ans[100];
int Test;
int main(){
Read(Test);
while(Test--){
int n,m;
Read(n);Read(m);
for(RG i=1;i<=n;++i){
Read(a[i]);
Read(b[i]);
Read(c[i]);
}
Range.clear();
for(RG i=1;i<=n;++i){
int center=-b[i]/(a[i]<<1);
int Len=n/2+2;
if(center-Len+1<1){
for(RG j=1;j<=min(m,Len*2);++j)
Range.push_back(j);
}else if(center+Len-1>m){
for(RG j=max(1,m-Len*2);j<=m;++j)
Range.push_back(j);
}else{
for(RG j=max(1,center-Len+1);j<=min(m,center+Len-1);++j)
Range.push_back(j);
}
}
sort(Range.begin(),Range.end());
Range.erase(unique(Range.begin(),Range.end()),Range.end());
int Num=Range.size();
cnt=2;
memset(head,0,sizeof(head));
MaxFlow=MinCost=0;
for(RG i=0;i<Num;++i){
LL x=Range[i];
for(RG j=1;j<=n;++j){
LL w=a[j]*x*x+b[j]*x+c[j];
add_edge(Num+j,i+1,1,w);
add_edge(i+1,Num+j,0,-w);
}
}
N=Num+n+3;
int SS=Num+n+1;
T=Num+n+2;S=Num+n+3;
for(RG i=1;i<=n;++i){
add_edge(SS,Num+i,1,0);
add_edge(Num+i,SS,0,0);
}
for(RG i=1;i<=Num;++i){
add_edge(i,T,1,0);
add_edge(T,i,0,0);
}
add_edge(SS,S,0,0);
add_edge(S,SS,n,0);
for(RG k=1;k<=n;++k){
if(SPFA()) Update();
printf("%lld",MinCost);
if(k<n) printf(" ");
}
printf("\n");
}
return 0;
}