A. Array Merge
题目大意
给出长度为n的序列A和长度为m的序列B。现在要求将这两个序列在保持序列内顺序不变的情况下,合并成一个长度为n+m的大序列C。并且最小化cost,其中cost为:
c
o
s
t
=
∑
i
=
1
n
+
m
i
×
C
[
i
]
cost=\sum_{i=1}^{n+m}i\times C[i]
cost=i=1∑n+mi×C[i]
求这个cost。
声明
该题解中的“子序列”,指的是“连续子序列”,可以理解为子段。
类比问题
如果我们有两个序列A和B,满足他们都是从大到小排好序的,现在要求合并序列,那我们可以很轻松的想到贪心的做法:双指针扫描,不断选择较大的数,直到两个指针都指向各自序列的末尾。
性质
我们试想:如果这两个序列已经以某种方式穿插在一起了,我们现在可以通过一些交换相邻子序列来使得cost变小。由于两个序列原序列相对位置不变,那么该交换一定是一段序列A和一段序列B之间的交换。
现在假如我们要交换两段子序列:a[i]a[j]和b[l]b[r],我们可以计算出它的局部cost:tc。
注:tc是templecost的简写
交换前有:
t
c
1
=
a
[
i
]
∗
(
i
+
l
−
1
)
+
a
[
i
+
1
]
∗
(
i
+
l
)
+
…
+
a
[
j
]
∗
(
j
+
l
−
1
)
+
b
[
l
]
∗
(
j
+
l
)
+
b
[
l
+
1
]
∗
(
j
+
l
+
1
)
+
…
+
b
[
r
]
∗
(
j
+
r
)
tc1=a[i]*(i+l-1)+a[i+1]*(i+l)+…+a[j]*(j+l-1)+b[l]*(j+l)+b[l+1]*(j+l+1)+…+b[r]*(j+r)
tc1=a[i]∗(i+l−1)+a[i+1]∗(i+l)+…+a[j]∗(j+l−1)+b[l]∗(j+l)+b[l+1]∗(j+l+1)+…+b[r]∗(j+r)
交换后有:
t
c
2
=
b
[
l
]
∗
(
i
+
l
−
1
)
+
b
[
l
+
1
]
∗
(
i
+
l
)
+
…
+
b
[
r
]
∗
(
i
+
r
−
1
)
+
a
[
i
]
∗
(
i
+
r
)
+
a
[
i
+
1
]
∗
(
i
+
1
+
r
)
+
…
+
a
[
j
]
∗
(
j
+
r
)
tc2=b[l]*(i+l-1)+b[l+1]*(i+l)+…+b[r]*(i+r-1)+a[i]*(i+r)+a[i+1]*(i+1+r)+…+a[j]*(j+r)
tc2=b[l]∗(i+l−1)+b[l+1]∗(i+l)+…+b[r]∗(i+r−1)+a[i]∗(i+r)+a[i+1]∗(i+1+r)+…+a[j]∗(j+r)
两式相减
t
c
2
−
t
c
1
=
∑
p
=
i
j
a
[
p
]
∗
(
r
−
l
+
1
)
−
∑
p
=
l
r
b
[
p
]
∗
(
j
−
i
+
1
)
tc2-tc1=\sum_{p=i}^{j}a[p]*(r-l+1)-\sum_{p=l}^{r}b[p]*(j-i+1)
tc2−tc1=p=i∑ja[p]∗(r−l+1)−p=l∑rb[p]∗(j−i+1)
因此如果有
∑
p
=
i
j
a
[
p
]
∗
(
r
−
l
+
1
)
>
∑
p
=
l
r
b
[
p
]
∗
(
j
−
i
+
1
)
\sum_{p=i}^{j}a[p]*(r-l+1)>\sum_{p=l}^{r}b[p]*(j-i+1)
p=i∑ja[p]∗(r−l+1)>p=l∑rb[p]∗(j−i+1)
那么交换能使得cost变大。我们两边同时除以(r - l + 1) * (j - i + 1), 有:
∑
p
=
i
j
a
[
p
]
j
−
i
+
1
>
∑
p
=
l
r
b
[
p
]
r
−
l
+
1
\frac{\sum_{p=i}^{j}a[p]}{j-i+1}>\frac{\sum_{p=l}^{r}b[p]}{r-l+1}
j−i+1∑p=ija[p]>r−l+1∑p=lrb[p]
我们可以惊喜的发现,这就是两段子序列的平均数比较大小!
联系类比问题和性质
如果说我们能够将两个序列各自先进行预处理的合并,使每个序列都分成若干个连续子序列,使得这些连续子序列的平均数递减,并且因为贪心的思想,要让前面的子序列平均数尽可能的大,那么我们就可以直接转化成类比问题:
两个指针分别扫描这些连续子序列,比较两个指针所指的连续子序列平均数,取平均数大者。
如何预处理合并
我们设想,如果有一个数夹在两个连续子序列中间,我们应该如何合并它呢?
根据贪心原理,如果它合并到前一个子序列中能使得该子序列平均数变大,那么合并到前一个子序列中;如果它合并到后一个子序列中能使得该子序列平均数更小,那么合并到后一个子序列中,否则它将单独成段。
那么我们就可以确定贪心地预处理策略:先让每一个元素独立成块,从后往前扫,对于两个挨着的块:如果后一个块的平均数大于前一个块的平均数,那么合并这两个块。不断进行上述操作,直到不能合并。这里直到不能合并的意思是:需要进行多次从后往前扫描的动作。
预处理合并完毕后,就是满足类比问题的性质的两个序列了。
code
#include<bits/stdc++.h>
using namespace std;
int read () {
int num = 0; char c = ' '; int flag = 1;
for (;c > '9' || c < '0'; c = getchar ())
if (c == '-')
flag = 0;
for (;c >= '0' && c <= '9'; num = (num << 3) + (num << 1) + c - 48, c = getchar ());
return flag ? num : - num;
}
typedef long long ll;
const int maxn = 100200;
ll a[maxn], b[maxn];
int n, m; ll x[maxn], y[maxn];
void init () {
n = read (), m = read ();
for (int i = 1;i <= n;i ++)
x[i] = a[i] = read ();
for (int i = 1;i <= m;i ++)
y[i] = b[i] = read ();
}
int la[maxn], lb[maxn];
void mergea () {
for (int i = 1;i <= n;i ++)
la[i] = 1;
int k = n; bool flag = 1;
while (flag) {
flag = 0; k = n;
for (int i = n - 1;i >= 1;i --)
if (a[i]) {
if (1ll * a[k] * la[i] > 1ll * a[i] * la[k]) {
a[k] += a[i]; a[i] = 0;
la[k] += la[i]; la[i] = 0;
flag = 1;
}
else k = i;
}
}
}
void mergeb () {
for (int i = 1;i <= m;i ++)
lb[i] = 1;
int k = m; bool flag = 1;
while (flag) {
flag = 0; k = m;
for (int i = m - 1;i >= 1;i --)
if (b[i]) {
if (1ll * b[k] * lb[i] > 1ll * b[i] * lb[k]) {
b[k] += b[i]; b[i] = 0;
lb[k] += lb[i]; lb[i] = 0;
flag = 1;
}
else k = i;
}
}
}
ll blocka[maxn], blockb[maxn], topa, topb;
void prework () {
topa = topb = 0;
int t = 0;
for (int i = 1;i <= n;i ++) {
t ++;
if (a[i]) blocka[++ topa] = t, t = 0;
}
t = 0;
for (int i = 1;i <= m;i ++) {
t ++;
if (b[i]) blockb[++ topb] = t, t = 0;
}
for (int i = 1;i <= n;i ++)
a[i] = x[i];
for (int i = 1;i <= m;i ++)
b[i] = y[i];
for (int i = 1;i <= n;i ++)
x[i] += x[i - 1];
for (int i = 1;i <= m;i ++)
y[i] += y[i - 1];
}
long long ans;
void work () {
int i, j, k, l;
i = j = k = l = 1;
ans = 0; int s = 1;
while (k <= topa || l <= topb) {
long long sum1, sum2;
sum1 = 1ll * (x[i + blocka[k] - 1] - x[i - 1]) * blockb[l];
sum2 = 1ll * (y[j + blockb[l] - 1] - y[j - 1]) * blocka[k];
if (l > topb) {
for (int u = 1;u <= blocka[k];u ++) {
ans += 1ll * a[i ++] * s ++;
}
k ++;
}
else if (k > topa) {
for (int u = 1;u <= blockb[l];u ++) {
ans += 1ll * b[j ++] * s ++;
}
l ++;
}
else {
if (sum1 > sum2) {
for (int u = 1;u <= blocka[k];u ++) {
ans += 1ll * a[i ++] * s ++;
}
k ++;
}
else {
for (int u = 1;u <= blockb[l];u ++) {
ans += 1ll * b[j ++] * s ++;
}
l ++;
}
}
}
}
int main () {
int T = read ();
for (int caseT = 1;caseT <= T;caseT ++) {
init ();
mergea ();
mergeb ();
prework ();
work ();
printf ("Case %d: %lld\n", caseT, ans);
}
return 0;
}