【BOI2011】timeismoney (最小乘積生成樹)
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
The Solution
題目大意
給定我們一些邊與每條邊上的兩個權值xi,yi,連成一幅無向連通圖,求在圖中找一棵最小生成樹,使得
Analysis
就是讓我們求最小乘積生成樹
這不就擺明了是道裸體嘛QQ~~,
直接裸奔就好了
下面來普及(口胡)一下最小乘積生成樹
就拿這道題為例來說吧
我們可以把每條邊的權值描述為一個二元組(xi,yi),把生成樹轉化為平面內的點,然後把它投影到一個平面直角座標系上,橫座標表示
則問題轉化為求一個點,使得xy=k最小,換句話說,就是使得過這個點的反比例函式
因此我們需要求出所有這些點構成的凸包的左下部分,從中找一個最大的。
接著我們就切入正題了
1、先求出分別距x軸和y軸最近的生成樹(點):A,B
實際操作可以分別按x權值和y權值做最小生成樹。
怎麼求凸包的左下部分呢?
用分治法!!!
分治大法好!
分治大法好!
分治大法好!
2、我們可以尋找一個在AB的靠近原點的一側且離AB最遠的點C(生成樹)。遞迴分治更新答案
怎麼找C點呢?
由於C離AB最遠,所以S△ABC面積最大。
向量AB、AC的叉積(的二分之一)為S△ABC的面積(只不過叉積是有向的,是負的,所以最小化這個值,即為最大化面積)。
即最小化:
所以將每個點的權值修改為
至於方案數的話,開個陣列記錄一下就好了。
參考CODE
#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define fd(i,a,b) for (int i=a;i>=b;i--)
#define N 10005
#define INF 1 << 30
using namespace std;
typedef long long ll;
struct Edge
{
int from,to,c,t;
ll z;
}E[N];
struct Node
{
int x,y;
}Minx,Miny,Ans;
int Res[N][3];
int n,m;
int Dad[N];
int read(int &n)
{
char ch = ' ';
int q = 0, w = 1;
for (;(ch != '-') && ((ch < '0') || (ch> '9'));ch = getchar());
if (ch == '-') w = -1,ch = getchar();
for (; ch >= '0' && ch <= '9';ch = getchar()) q = q * 10 + ch - 48;
n = q * w;
return n;
}
int Get(int x)
{
if (Dad[x] == x) return x;
else return Dad[x] = Get(Dad[x]);
}
bool cmp1(Edge a,Edge b)
{
return a.t < b.t;
}
bool cmp2(Edge a,Edge b)
{
return a.c < b.c;
}
bool cmp3(Edge a,Edge b)
{
return a.z < b.z;
}
Node Kruskal()
{
int tot = 0;
Node G = {0,0};
static int Mark[N][3];
memset(Mark,0,sizeof(Mark));
fo(i,1,n) Dad[i] = i;
fo(i,1,m)
{
int xx = Get(E[i].from),
yy = Get(E[i].to);
if (xx != yy)
{
Dad[xx] = yy;
tot ++;
G.x += E[i].t;
G.y += E[i].c;
Mark[++ Mark[0][0]][1] = E[i].from - 1;
Mark[Mark[0][0]][2] = E[i].to - 1;
if (tot == n - 1) break;
}
}
ll t1 = (ll)Ans.x * Ans.y,
t2 = (ll)G.x * G.y;
if (t2 < t1 || (t2 == t1 && G.x < Ans.x))
{
memcpy(Res,Mark,sizeof(Mark));
Ans = G;
}
return G;
}
ll Chaji(Node A,Node B,Node C)
{
return ((ll)B.x - A.x) * ((ll)C.y - A.y ) - ((ll)B.y - A.y) * ((ll)C.x - A.x);
}
void Work(Node a,Node b)
{
fo(i,1,m)
E[i].z = E[i].c * (b.x - a.x) + E[i].t * (a.y - b.y);
sort(E + 1,E + m + 1,cmp3);
Node mid = Kruskal();
if (Chaji(a,b,mid) >= 0) return;
Work(a,mid);
Work(mid,b);
}
int main()
{
freopen("timeismoney.in","r",stdin);
freopen("timeismoney.out","w",stdout);
Ans.x = Ans.y = INF;
read(n),read(m);
fo(i,1,m)
{
int u,v;
read(u),read(v),read(E[i].t),read(E[i].c);
E[i].from = ++ u;
E[i].to = ++ v;
}
sort(E + 1,E + m + 1,cmp1);
Minx = Kruskal();
sort(E + 1,E + m + 1,cmp2);
Miny = Kruskal();
Work(Minx,Miny);
printf("%d %d\n",Ans.x,Ans.y);
fo(i,1,Res[0][0]) printf("%d %d\n",Res[i][1],Res[i][2]);
return 0;
}