传送门:bzoj2395
题解
一篇很好的题解
主要采用了数形结合思想,将总代价看做二维平面上的一个点, ∑ c \sum c ∑c相当于 x x x坐标, ∑ t \sum t ∑t相当于 y y y坐标。每次都是在一个下凸壳上找最小方案:具体来说,首先找到 ∑ c \sum c ∑c最小的点 A A A(按 c c c做MST),以及 ∑ t \sum t ∑t最小的点 B B B(按 t t t做MST),如果有更优点 C C C,必然在有向线段 A B AB AB右侧,贪心找到离 A B AB AB最远的 C C C,即 c r o s s ( B − A , C − A ) cross(B-A,C-A) cross(B−A,C−A)最小:
忽略
c
r
o
s
s
(
B
−
A
,
C
−
A
)
cross(B-A,C-A)
cross(B−A,C−A)中的常数项,相当于使得
(
B
x
−
A
x
)
C
y
+
(
A
y
−
B
y
)
C
x
(Bx-Ax)Cy+(Ay-By)Cx
(Bx−Ax)Cy+(Ay−By)Cx最小。
于是边权设为
(
B
x
−
A
x
)
t
i
+
(
A
y
−
B
y
)
c
i
(Bx-Ax)t_i+(Ay-By)c_i
(Bx−Ax)ti+(Ay−By)ci做一遍MST求出
C
C
C,如果
C
C
C在
A
B
AB
AB右侧,那么迭代下去找
A
C
,
C
B
AC,CB
AC,CB右侧的点,直到右侧没有点。这个过程中的所有点都可能更新答案。
复杂度 O ( 凸 包 上 点 × ( m log m + n α ( n ) ) ) O(凸包上点\times (m\log m+n\alpha(n))) O(凸包上点×(mlogm+nα(n)))
代码
点下标是从0开始的,改了好久才发现。。。
#include<bits/stdc++.h>
using namespace std;
const int N=202,M=1e4+10,inf=1e9;
typedef long long ll;
int n,m,f[N];
struct P{
int x,y;
P(int x_=0,int y_=0):x(x_),y(y_){};
inline P operator -(const P&ky){return P(x-ky.x,y-ky.y);}
inline ll operator ^(const P&ky){return (ll)x*ky.y-(ll)y*ky.x;}
}ans,mn,mx;
inline void chk(P &a,P b){
ll re=(ll)a.x*a.y-(ll)b.x*b.y;
if(re>0 || (re==0 && b.x<a.x)) a=b;
}
struct pr{
int u,v,c,t,w;
bool operator<(const pr&ky)const{return w<ky.w;}
}le[M];
inline int F(int x){return (x==f[x])?x:(f[x]=F(f[x]));}
inline P MST()
{
int i,x,y,k=0;P re=P(0,0);
sort(le+1,le+m+1);
for(i=0;i<n;++i) f[i]=i;
for(i=1;k+1<n && i<=m;++i){
x=F(le[i].u);y=F(le[i].v);
if(x==y) continue;
f[x]=y;k++;
re.x+=le[i].c;re.y+=le[i].t;
}
if(k+1<n) re=P(inf,inf);//
return re;
}
void sol(P a,P b)
{
int i;P c;
for(i=1;i<=m;++i)
le[i].w=(b.x-a.x)*le[i].t+(a.y-b.y)*le[i].c;
c=MST();
if(((b-a)^(c-a))>=0) return;
chk(ans,c);sol(a,c);sol(c,b);
}
int main(){
int i,j,x,y;ans=P(inf,inf);
scanf("%d%d",&n,&m);
for(i=1;i<=m;++i)
scanf("%d%d%d%d",&le[i].u,&le[i].v,&le[i].c,&le[i].t);
for(i=1;i<=m;++i) le[i].w=le[i].c;mn=MST();
for(i=1;i<=m;++i) le[i].w=le[i].t;mx=MST();
chk(ans,mn);chk(ans,mx);sol(mn,mx);
printf("%d %d",ans.x,ans.y);
return 0;
}