【最小乘積生成樹】bzoj2395[Balkan 2011]Timeismoney


 設每個點有x,y兩個權值,求一棵生成樹,使得sigma(x[i])*sigma(y[i])最小。

 

設每棵生成樹為坐標系上的一個點,sigma(x[i])為橫坐標,sigma(y[i])為縱坐標。則問題轉化為求一個點,使得xy=k最小。即,使過這個點的反比例函數y=k/x最接近坐標軸。

 

Step1:求得分別距x軸和y軸最近的生成樹(點):AB(分別按x權值和y權值做最小生成樹即可)。

 

Step2:尋找一個在AB的靠近原點一側的且離AB最遠的生成樹C,試圖更新答案。

 

【怎么找????

——由於CAB最遠,所以SABC面積最大。

向量AB=B.x - A.x , B.y - A.y

向量AC= (C.x - A.x , C.y - A.y)

向量ABAC的叉積(的二分之一)SABC的面積(只不過叉積是有向的,是負的,所以最小化這個值,即為最大化面積)。

 

最小化:(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。】

 

Step3:遞歸地分別往ACBC靠近原點的一側找。遞歸邊界:該側沒有點了(即叉積大於等於零)

 

BZOJ2395 裸題

Code:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5 int res;
 6 char c;
 7 inline int Get()
 8 {
 9     res=0;c='*';
10     while(c<'0'||c>'9')c=getchar();
11     while(c>='0'&&c<='9'){res=res*10+(c-'0');c=getchar();}
12     return res;
13 }
14 struct Edge{int u,v,c,t,w;void read(){u=Get();v=Get();c=Get();t=Get();}};
15 struct Point{int x,y;Point(const int &A,const int &B){x=A;y=B;}Point(){}};
16 typedef Point Vector;
17 typedef long long LL;
18 Vector operator - (const Point &a,const Point &b){return Vector(a.x-b.x,a.y-b.y);}
19 int Cross(Vector A,Vector B){return A.x*B.y-A.y*B.x;}
20 bool operator < (const Edge &a,const Edge &b){return a.w<b.w;}
21 Edge edges[10001];
22 int n,m,rank[201],fa[201];
23 Point ans=Point(1000000000,1000000000),minc,mint;
24 inline void init()
25 {
26     memset(rank,0,sizeof(rank));
27     for(int i=0;i<n;i++)
28       fa[i]=i;
29 }
30 int findroot(int x) 
31 {
32     if(fa[x]==x)
33       return x;
34     int t=findroot(fa[x]);
35     fa[x]=t;
36     return t;
37 }
38 inline void Union(int U,int V)
39 {
40     if(rank[U]<rank[V])
41       fa[U]=V;
42     else
43       {
44         fa[V]=U;
45         if(rank[U]==rank[V])
46           rank[U]++;
47       }
48 }
49 inline Point Kruscal()
50 {
51     int tot=0;
52     Point now=Point(0,0);
53     init();
54     for(int i=1;i<=m;i++)
55       {
56           int U=findroot(edges[i].u),V=findroot(edges[i].v);
57           if(U!=V)
58             {
59                 Union(U,V);
60                 tot++;
61                 now.x+=edges[i].c;
62                 now.y+=edges[i].t;
63                 if(tot==n-1)
64                   break;
65             }
66       }
67     LL Ans=(LL)ans.x*ans.y,Now=(LL)now.x*now.y;
68     if( Ans>Now || (Ans==Now&&now.x<ans.x) )
69       ans=now;
70     return now;
71 }
72 void Work(Point A,Point B)
73 {
74     for(int i=1;i<=m;i++)
75       edges[i].w=edges[i].t*(B.x-A.x)+edges[i].c*(A.y-B.y);
76     sort(edges+1,edges+m+1);
77     Point C=Kruscal();
78     if(Cross(B-A,C-A)>=0)
79       return;
80     Work(A,C);
81     Work(C,B);
82 }
83 int main()
84 {
85     scanf("%d%d",&n,&m);
86     for(int i=1;i<=m;i++)
87       edges[i].read();
88     for(int i=1;i<=m;i++)
89       edges[i].w=edges[i].c;
90     sort(edges+1,edges+m+1);
91     minc=Kruscal();
92     for(int i=1;i<=m;i++)
93       edges[i].w=edges[i].t;
94     sort(edges+1,edges+m+1);
95     mint=Kruscal();
96     Work(minc,mint);
97     printf("%d %d\n",ans.x,ans.y);
98     return 0;
99 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM