题意:每条边有两个权值a,b,求图的最小二元和乘积生成树(即该树的sum_a*sum_b最小)。
标程:
1 #include<bits/stdc++.h> 2 #define P pair<ll,ll> 3 #define fir first 4 #define sec second 5 using namespace std; 6 typedef long long ll; 7 int read() 8 { 9 int x=0,f=1;char ch=getchar(); 10 while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();} 11 while (ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar(); 12 return x*f; 13 } 14 const int N=10005; 15 int f[N],n,m; 16 ll ans,qa,qb; 17 struct node{ll u,v,a,b,w;}e[N]; 18 int find(int x){return x==f[x]?x:f[x]=find(f[x]);} 19 bool cmp_a(const node &A,const node &B){return A.a<B.a;} 20 bool cmp_b(const node &A,const node &B){return A.b<B.b;} 21 bool cmp_w(const node &A,const node &B){return A.w<B.w;} 22 ll chaji(P A,P B,P C){return (B.fir-A.fir)*(C.sec-A.sec)-(B.sec-A.sec)*(C.fir-A.fir);} 23 P mst() 24 { 25 ll s1=0,s2=0; 26 for (int i=1;i<=n;i++) f[i]=i; 27 for (int i=1;i<=m;i++) 28 if (find(e[i].u)!=find(e[i].v)) 29 f[find(e[i].u)]=find(e[i].v),s1+=e[i].a,s2+=e[i].b; 30 ll t=s1*s2; 31 if (t<ans) ans=t,qa=s1,qb=s2; 32 else if (t==ans&&s1<qa) qa=s1,qb=s2; 33 return P(s1,s2); 34 } 35 void solve(P A,P B) 36 { 37 for (int i=1;i<=m;i++) 38 e[i].w=(B.fir-A.fir)*e[i].b-(B.sec-A.sec)*e[i].a; 39 sort(e+1,e+m+1,cmp_w);P C=mst(); 40 if (chaji(A,B,C)>=0) return; 41 solve(A,C);solve(C,B); 42 } 43 int main() 44 { 45 n=read();m=read();ans=1ll<<60; 46 for (int i=1;i<=m;i++) e[i].u=read()+1,e[i].v=read()+1,e[i].a=read(),e[i].b=read(); 47 sort(e+1,e+m+1,cmp_a);P A=mst(); 48 sort(e+1,e+m+1,cmp_b);P B=mst(); 49 solve(A,B); 50 printf("%lld %lld\n",qa,qb); 51 return 0; 52 }
题解:数形结合+凸包
把每棵生成树按照(sum_a,sum_b)映射到坐标系上,基于乘积的反比例函数性质,动态维护下凸包。
具体地,先找到sum_a最小的点A和sum_b最小的点B,连边,在这条线的下方找到一个距离这条直线最远的点C。那么△ABC中的点一定不及三个端点的答案小。继续AC连边和CB连边找下方点,分治。并对端点统计。
难点在于最远点怎么求?转换成三角形面积最大,用叉积表示,其中一条边已知。向量AB叉乘向量AC=(B.x-A.x)*C.y-(B.y-A.y)*C.x-A.y*(B.x-A.x)+A.x*(B.y-A.y)(这是负的,使其最小)前两项和C有关,后两项都已知,重新对边赋权为(B.x-A.x)*e[i].b-(B.y-A.y)*e[i].a,跑mst,即为C点。
最坏复杂度O(nmlogm)。
如果是三元,那么就相当于在平面下找最远点,使得中间体积最大。分割的时候分成三个部分。