【学习笔记】最小乘积模型(凸包分治)
例题:P5540 [BalkanOI 2011] timeismoney
给出一个 \(n\) 个点 \(m\) 条边的无向图,第 \(i\) 条边有两个权值 \(a_i\) 和 \(b_i\) 。
求该图的一棵生成树 \(T\) ,使得
\[\left(\sum_{e\in T}a_e\right)\times\left(\sum_{e\in T}b_e\right) \]最小。
怎么说,感觉挺难想的。反正我做不出来
下面直接讲做法:
可以把 \(\sum a_e\) 和 \(\sum b_e\) 看作是 \(x,y\),对应平面上的点。
那么如果要使 \(x\times y\) 最小,那么这个点一定位于平面的下凸壳上。
但是由于平面上的点很多,不能一个个的枚举点。
可以先求出两个点 \(A,B\),分别对应 \(x\) 最小,和 \(y\) 最小的点,那显然这两个点一定在凸包上。那么剩下的点一定在两个点连线的左下方。
我们可以找到距离直线最远一个点,那么它一定也在凸包上,然后往下递归直到找不到点为止。
然后考虑如何找到这个点。
我们将其设为点 \(C\),距离 \(\mathrm{AB}\) 最远的点与 \(A,B\) 连成的三角形的面积一定是最大的,即 \(S=-\overrightarrow{\mathrm{AB}}\times \overrightarrow{\mathrm{AC}}\)。相当于要求 \(\overrightarrow{\mathrm{AB}}\times \overrightarrow{\mathrm{AC}}\) 最小。
展开,\(\overrightarrow{\mathrm{AB}}\times \overrightarrow{\mathrm{AC}}=(x_B-x_A)(y_C-y_A)-(y_B-y_A)(x_B-x_A)=(y_A-y_B)x_C+(x_B-x_A)y_C+y_Bx_A-x_By_A\)
后一部分是常量,对大小关系没有影响,可以忽略掉。
可以转换为每一条边权是 \(w_C=(y_A-y_B)x_C+(x_B-x_A)y_C\) 然后跑一边最小生成树就好了。
代码:
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) ((x)&(-x))
using namespace std;
const int N=1e4+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
int n,m;
PII a[N];
ll w[N];
vector<int> to[N];
struct vec{
ll w,x,y;
inline vec(){w=x=y=0;}
inline vec(ll _w,ll _x,ll _y){
w=_w,x=_x,y=_y;
}
inline vec operator + (const vec&xx) const{
return vec(w+xx.w,x+xx.x,y+xx.y);
}
inline vec operator - (const vec&xx) const{
return vec(w-xx.w,x-xx.x,y-xx.y);
}
inline bool operator < (const vec&xx) const{
if(w==xx.w) return x<xx.x;
return w<xx.w;
}
inline ll operator * (const vec&xx) const{
return x*xx.y-y*xx.x;
}
};
ll ans,sumx,sumy;
struct edge{
int x,y,id;
inline bool operator < (const edge &xx) const{
if(w[id]==w[xx.id]){
if(a[id].first==a[xx.id].first) return a[id].second<a[xx.id].second;
return a[id].first<a[xx.id].first;
}
return w[id]<w[xx.id];
}
}e[N];
int ff[N];
inline int find(int x){
if(x==ff[x]) return x;
return ff[x]=find(ff[x]);
}
inline vec calc(ll x,ll y){
for(int i=1;i<=m;i++) w[i]=1ll*x*a[i].first+1ll*y*a[i].second;//转换点权
sort(e+1,e+1+m);
iota(ff,ff+n,0);
vec res;
for(int i=1;i<=m;i++){//最小生成树
auto [u,v,id]=e[i];
int fu=find(u),fv=find(v);
if(fu==fv) continue;
ff[fu]=fv;
res=res+vec(w[id],a[id].first,a[id].second);
}
return res;
}
inline void solve(vec A,vec B){
if(A.x*B.y>=ans) return ;//优化剪枝,会快很多
if(A.y-B.y<=0 || B.x-A.x<=0) return ;
vec C=calc(A.y-B.y,B.x-A.x);
if(ans==C.x*C.y && sumx>C.x) sumx=C.x,sumy=C.y;
if(ans>C.x*C.y) ans=C.x*C.y,sumx=C.x,sumy=C.y;
if((B-A)*(C-A)>=0) return ;
solve(A,C);solve(C,B);
}
int main(){
cin>>n>>m;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v>>a[i].first>>a[i].second;
e[i]={u,v,i};
}
ans=sumx=sumy=INF;
vec A=calc(1,0),B=calc(0,1);
ans=min(A.x*A.y,B.x*B.y);
if(ans==A.x*A.y && sumx>A.x) sumx=A.x,sumy=A.y;
if(ans==B.x*B.y && sumx>B.x) sumx=B.x,sumy=B.y;
solve(A,B);
cout<<sumx<<" "<<sumy<<"\n";
return 0;
}
}
signed main(){
IOS;H_H::main();
return 0;
}

浙公网安备 33010602011771号