题目
传送门:有账号才行。
题目描述 幻想乡有
N
N
N 个区域,从
1
1
1 到
N
N
N 编号,神奈子希望通过修建“御神渡”来让所有的区域互相可达,“御神渡”可以看做两个区域之间的一个双向道路。在
i
i
i 区域与
j
j
j 区域之间修建“御神渡”,需要在
i
i
i 区域耗费
C
i
C_i
Ci 的神力新建神社,在
j
j
j 区域耗费
C
j
C_j
Cj 的神力新建神社,然后将两个神社配对。如果一个区域和其他多个区域建立“御神渡”,那么在这个区域要为每个“御神渡”建一个神社,即多个“御神渡”不能共用神社。
“御神渡”会促进两个区域的人口流动,而人口的流动就会带来文化的交流、信仰的增多。如果
i
i
i 区域的人口为
A
i
A_i
Ai 而
j
j
j 区域有的人口为
A
j
A_j
Aj ,则神奈子将会得到
A
i
×
A
j
A_i\times A_j
Ai×Aj 的神力。
神奈子希望修建尽量少的“御神渡”,使得
N
N
N 个区域互相可达。在这个前提下,神奈子还希望自己总神力的减少量最小(注意该值可能为负,即神奈子的神力不减反增)。
输入格式 第一行一个非负整数
N
N
N ,表示区域的数量。
第二行
N
N
N 个非负整数
A
i
A_i
Ai ,分别表示
N
N
N 个区域的人口。数据保证每个区域的人口互不相等。
第三行
N
N
N 个非负整数
C
i
C_i
Ci ,分别表示在
N
N
N 个区域建神社要花费的神力。
输出格式 输出一个整数,表示神奈子总神力的减少量的最小值。
数据范围与约定 对于所有数据,
N
≤
1
0
5
,
A
i
≤
2
×
1
0
6
,
C
i
≤
1
0
9
N\le 10^5,A_i\le 2\times10^6,C_i\le 10^9
N≤105,Ai≤2×106,Ci≤109 。
思路
很明显,将
(
u
,
v
)
(u,v)
(u,v) 的边权视作
C
u
+
C
v
−
A
u
×
A
v
C_u+C_v-A_u\times A_v
Cu+Cv−Au×Av(即在
u
u
u 和
v
v
v 之间建立“御神渡”的净花费),则原题等价于求这张图里的最小生成树。
耿直的暴力
暴力求最小生成树,
O
(
n
2
)
\mathcal O(n^2)
O(n2) 。因为是完全图。
斜率优化
思考为什么这道题可以有比最小生成树更快的算法?必然是因为 边权的特殊性。
不难证明,所有与
u
u
u 相连的边中最小的一条一定是生成树中的边。(可以用
p
r
i
m
\tt prim
prim 或
k
r
u
s
k
a
l
\tt kruskal
kruskal 的过程证明,或模仿二者的反证法来证明)。怎么找最小的一条边?
考虑 斜率优化。推一推斜率优化的式子,发现是
C
u
−
C
v
<
A
x
(
A
u
−
A
v
)
C_u-C_v<A_x(A_u-A_v)
Cu−Cv<Ax(Au−Av) 时
u
u
u 更优(考虑边
⟨
x
,
u
⟩
\langle x,u\rangle
⟨x,u⟩ 和
⟨
x
,
v
⟩
\langle x,v\rangle
⟨x,v⟩ 谁边权更小)。那么假定
A
v
<
A
u
A_v<A_u
Av<Au ,第
x
x
x 个点定义为
(
C
x
,
A
x
)
(C_x,A_x)
(Cx,Ax) ,上式等价于
K
u
v
<
A
x
K_{uv}<A_x
Kuv<Ax 。不难发现,维护的形状是下凸包。
先求凸包,按照
A
A
A 值排序后跑,
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn) 。然而现实很残酷——只有
60
p
t
s
\tt{60pts}
60pts !
改进版
为什么不是
100
100
100 分到手?因为有可能 试图连接自环!
也就是说,斜率优化求决策点的时候,最佳决策点是自己 我也很绝望啊。
那么我们迫切的需要一个新方法:考虑每一个点的时候,只把其他的拿出来做凸包。回到了
O
(
n
2
)
\mathcal O(n^2)
O(n2) ……
等等!好像对于
[
1
,
m
i
d
]
[1,mid]
[1,mid] 中的每一个
x
x
x ,
(
m
i
d
,
r
]
(mid,r]
(mid,r] 中的任意一个点都可以当做决策点。这提示我们 分治!
用
w
o
r
k
(
l
,
r
)
work(l,r)
work(l,r) 表示处理
[
l
,
r
]
[l,r]
[l,r] 内部的互相贡献,首先递归,其次 交叉计算贡献。
这很像
c
d
q
\tt{cdq}
cdq分治,能做到
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn) 。应该有
80
80
80 分了吧?
正解
为什么还不过,去™的垃圾题
因为 连接一次不够……有可能只连接了
n
2
\frac{n}{2}
2n 条边(两个点 选择了同一条边……)
好吧,把点染色,一个联通块中的点染一种颜色,问题转化为 某一颜色向异色点中连边。
我们把颜色拿来排个序,一样可以分治……个屁!为什么不能分治了?因为交叉计算贡献的时候,得依靠
A
A
A 值有序才行!
这个问题好像有点熟悉……有两个维度
(
x
,
y
)
(x,y)
(x,y),要求
x
x
x 和
y
y
y 都有序。二维偏序?
做过二维偏序板题吗?做过的都明白用的方法是
c
d
q
\tt cdq
cdq分治!
现在这个题,好像有点板?(大雾)
C
D
Q
CDQ
CDQ 分治,先对颜色排序,再归并
A
A
A 值,
O
(
n
log
2
n
)
\mathcal O(n\log^2 n)
O(nlog2n) 。
关于复杂度:当前有
m
m
m 种颜色,则至少连接
m
2
\frac{m}{2}
2m 条边,也就是颜色减少一半。开始有
n
n
n 种颜色,最多做
log
n
\log n
logn 次生成树。生成树复杂度与上解法三同,
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn) 。实际上,很少有数据能够卡到做
log
n
\log n
logn 次生成树(大多数做个
2
2
2 到
5
5
5 次就搞定了),运行起来海星。
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std
;
struct Point
{
int x
, y
;
Point(int X
=0,int Y
=0):x(X
),y(Y
){ }
Point
operator+(const Point
&that
)const{
return Point(x
+that
.x
,y
+that
.y
);
}
Point
operator-(const Point
&that
)const{
return Point(x
-that
.x
,y
-that
.y
);
}
friend long long operator*(const Point
&a
,const Point
&b
){
return 1ll*a
.x
*b
.y
-1ll*a
.y
*b
.x
;
}
friend Point
operator*(const int &u
,const Point
&v
){
return Point(u
*v
.x
,u
*v
.y
);
}
};
const int MaxN
= 100000;
int allColor
, n
, fa
[MaxN
];
inline int findColor(int a
){
if(fa
[a
] != a
)
fa
[a
] = findColor(fa
[a
]);
return fa
[a
];
}
struct Node
{
Point p
; int color
;
bool operator < (const Node
&that
)const{
if(color
!= that
.color
)
return color
< that
.color
;
return this->lessThan(that
);
}
bool lessThan(const Node
&that
)const{
return p
.x
< that
.p
.x
;
}
} node
[MaxN
];
long long edgeValue(int i
,int j
){
return -1ll*node
[i
].p
.x
*node
[j
].p
.x
+node
[i
].p
.y
+node
[j
].p
.y
;
}
int ys
[MaxN
];
int* convexHull(int l
,int r
,int *result
){
int *top
= result
;
for(int i
=l
; i
<r
; ++i
){
while(top
-result
>= 2)
if((node
[i
].p
-node
[*(top
-1)].p
)*(node
[*(top
-1)].p
-node
[*(top
-2)].p
) >= 0)
-- top
;
else break;
*(top
++) = i
;
}
return top
;
}
int temp
[MaxN
];
pair
<long long,int> choice
[MaxN
];
Node temp2
[MaxN
];
void dealWith(int L
,int R
,int l
,int r
){
if(L
== R
) return ;
int MID
= (L
+R
)>>1, mid
;
for(mid
=l
; mid
<r
; ++mid
)
if(ys
[node
[mid
].color
] > MID
)
break;
dealWith(L
,MID
,l
,mid
);
dealWith(MID
+1,R
,mid
,r
);
Point xl
;
for(int i
=mid
, *now
=temp
, *tail
=convexHull(l
,mid
,temp
); i
<r
; ++i
){
xl
= Point(1,node
[i
].p
.x
);
while(tail
-now
>= 2)
if((node
[*(now
+1)].p
-node
[*now
].p
)*xl
>= 0)
++ now
; else break;
if(choice
[node
[i
].color
].second
== -1 or edgeValue(i
,*now
) < choice
[node
[i
].color
].first
)
choice
[node
[i
].color
] = make_pair(edgeValue(i
,*now
),node
[*now
].color
);
}
for(int i
=l
, *now
=temp
, *tail
=convexHull(mid
,r
,temp
); i
<mid
; ++i
){
xl
= Point(1,node
[i
].p
.x
);
while(tail
-now
>= 2)
if((node
[*(now
+1)].p
-node
[*now
].p
)*xl
>= 0)
++ now
;
else break;
if(choice
[node
[i
].color
].second
== -1 or edgeValue(i
,*now
)<choice
[node
[i
].color
].first
)
choice
[node
[i
].color
] = make_pair(edgeValue(i
,*now
),node
[*now
].color
);
}
for(int i
=l
, j
=mid
, k
=l
; i
<mid
or j
<r
; )
if(j
== r
or (i
< mid
and node
[i
].lessThan(node
[j
])))
temp2
[k
++] = node
[i
++];
else
temp2
[k
++] = node
[j
++];
for(int i
=l
; i
<r
; ++i
)
node
[i
] = temp2
[i
];
}
long long ans
= 0;
bool setMST(){
for(int i
=0; i
<n
; ++i
){
fa
[i
] = i
; ys
[i
] = 0;
choice
[i
].second
= -1;
}
sort(node
,node
+n
); allColor
= 0;
for(int i
=0; i
<n
; ){
ys
[node
[i
].color
] = ++allColor
;
for(; i
<n
and ys
[node
[i
].color
] == allColor
; ++i
);
}
if(allColor
== 1)
return true;
dealWith(1,allColor
,0,n
);
for(int i
=0; i
<n
; ++i
){
if(not ys
[node
[i
].color
])
continue;
ys
[node
[i
].color
] = 0;
if(choice
[choice
[node
[i
].color
].second
].second
== node
[i
].color
and node
[i
].color
> choice
[node
[i
].color
].second
)
continue;
fa
[node
[i
].color
] = choice
[node
[i
].color
].second
;
ans
+= choice
[node
[i
].color
].first
;
}
for(int i
=0; i
<n
; ++i
)
node
[i
].color
= findColor(node
[i
].color
);
return false;
}
int main(){
scanf("%d",&n
);
for(int i
=0; i
<n
; ++i
){
node
[i
].color
= i
;
scanf("%d",&node
[i
].p
.x
);
}
for(int i
=0; i
<n
; ++i
)
scanf("%d",&node
[i
].p
.y
);
while(not setMST());
cout
<< ans
;
return 0;
}