3621. 【BOI2011】timeismoney
(File IO): input:timeismoney.in output:timeismoney.out
Time Limits: 2000 ms Memory Limits: 65536 KB Detailed Limits Special Judge
Description
NetLine 公司想要给N 个城镇提供宽带网络。为此,需要建造一个有N -1 条镇间宽带链接的网络,拥有一条消息能在这个网络上从任意镇传到任意镇的性质。NetLine 已经鉴定了所有城镇对之间能够直接建立的链接。对于每个这样的可能链接,他们知道建造这个链接的费用和时间。
公司对使建造总时间(链接不能同时建造)和总费用最小化都感兴趣。因为他们不能决定要单独使用哪一个标准,所以他们决定采用如下公式计算一个网络的评估值:
SumTime = 建造选择的链接所花时间之和
SumMoney = 建造选择的链接所花金钱之和
V = SumTime * SumMoney
选择一些需要建造的链接,使得所建网络的评估值V 最小。
Input
输入的第一行包含整数N——城镇的个数和M——能够建造的链接数。城镇从0 到N - 1 编号。
接下来M 行中每一行含四个整数x, y, t 和c——意味着城镇x 可以耗费t 时间及c 费用与城镇y 建立链接。
Output
输出的第一行为两个数字:最优方案(那个评估值V 最小的)使用的总时间(SumTime)和总费用(SumMoney),用一个空格隔开。接下来N - 1 行描述要建造的链接。每行包含一对数字(x, y)描述一个需建造的链接(须在输入中描述的可建造链接内)。这些数对可以按任意顺序输出。
当有多个最优解存在时,你可以输出其中任意一个。
Sample Input
5 7
0 1 161 79
0 2 161 15
0 3 13 153
1 4 142 183
2 4 236 80
3 4 40 241
2 1 65 92
Sample Output
279 501
2 1
0 3
0 2
3 4
Data Constraint
• 1 <= N <= 200
• 1 <= M <= 10 000
• 0 <= x, y <= N - 1
• 1 <= t, c <= 255
• 一个测试点有M = N - 1
• 40% 的数据对于每个可建造链接有t = c
Solution
算法:最小乘积生成树
我们可以把每条边的权值描述为一个二元组(xi,yi),把生成树转化为平面内的点,然后把它投影到一个平面直角坐标系上,横坐标表示∑xi,纵坐标表示∑yi
则问题转化为求一个点,使得xy=k最小,换句话说,就是使得过这个点的反比例函数y=k/x最接近坐标轴。
因此我们需要求出所有这些点构成的凸包的左下部分,从中找一个最大的。
切入正题
1.先求出分别距x轴和y轴最近的生成树(点):A,B
实际操作可以分别按x权值和y权值做最小生成树。
2.我们可以寻找一个在AB的靠近原点的一侧且离AB最远的点C(生成树)。
怎么找C点呢?
由于C离AB最远,所以S△ABC面积最大。
向量AB=(B.x−A.x,B.y−A.y)
向量AC=(C.x−A.x,C.y−A.y)
向量AB、AC的叉积(的二分之一)为S△ABC的面积(只不过叉积是有向的,是负的,所以最小化这个值,即为最大化面积)。
即最小化:
(B.x−A.x)∗(C.y−A.y)−(B.y−A.y)∗(C.x−A.x)=(B.x−A.x)∗C.y+(A.y−B.y)∗C.x−A.y∗(B.x−A.x)+A.x∗(B.y−A.y)
所以将每个点的权值修改为y[i]∗(B.x−A.x)+(A.y−B.y)∗x[i] 做最小生成树,找到的即是C。
3.怎么求凸包的左下部分呢?分治。递归分治更新答案。分别往AC、BC靠近原点的一侧找。递归边界:该侧没有点了(即叉积大于等于零)。
Code
#include<algorithm>
#include<cstring>
#include<cstdio>
#define N 210
#define M 10010
#define ll long long
#define inf 1<<30
using namespace std;
ll i,n,m;
ll f[N],d[N][2],d1[N][2];
struct node {ll x,y,t,c,z;}a[M];
struct data {ll t,c;}mt,mc,ans;
bool cmpt(node p,node q) {return p.t<q.t;}
bool cmpc(node p,node q) {return p.c<q.c;}
bool cmpz(node p,node q) {return p.z<q.z;}
ll get(ll u)
{
if (f[u]!=u) f[u]=get(f[u]);
return f[u];
}
void merge(ll u,ll v)
{
ll fu=get(u),fv=get(v);
f[fu]=fv;
}
ll chaji(data A,data B,data C)
{
return (B.t-A.t)*(C.c-A.c)-(B.c-A.c)*(C.t-A.t);
}
data kruskal()
{
ll i,c=0;
data now={0,0};
for(i=1;i<=n;i++) f[i]=i;
i=0;
while (c<n-1)
{
i++;
if (get(a[i].x)!=get(a[i].y))
{
merge(a[i].x,a[i].y),c++;
now.t+=a[i].t,now.c+=a[i].c;
d1[c][0]=a[i].x,d1[c][1]=a[i].y;
}
}
ll s1=ans.t*ans.c,s2=now.t*now.c;
if (s2<s1)
{
ans=now;
memcpy(d,d1,sizeof(d));
}
return now;
}
void getans(data A,data B)
{
for(ll i=1;i<=m;i++) a[i].z=a[i].c*(B.t-A.t)+a[i].t*(A.c-B.c);
sort(a+1,a+1+m,cmpz);
data mid=kruskal();
if (chaji(A,B,mid)>=0) return;
getans(A,mid),getans(mid,B);
}
int main()
{
freopen("timeismoney.in","r",stdin);
freopen("timeismoney.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(i=1;i<=m;i++)
{
scanf("%lld%lld%lld%lld",&a[i].x,&a[i].y,&a[i].t,&a[i].c);
a[i].x++,a[i].y++;
}
ans.t=ans.c=inf;
sort(a+1,a+1+m,cmpt),mt=kruskal();
sort(a+1,a+1+m,cmpc),mc=kruskal();
getans(mt,mc);
printf("%lld %lld\n",ans.t,ans.c);
for(i=1;i<=n-1;i++) printf("%lld %lld\n",d[i][0]-1,d[i][1]-1);
}