ccpc2018桂林 A. Array Merge

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=1n+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+l1)+a[i+1](i+l)++a[j](j+l1)+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+l1)+b[l+1](i+l)++b[r](i+r1)+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) tc2tc1=p=ija[p](rl+1)p=lrb[p](ji+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=ija[p](rl+1)>p=lrb[p](ji+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} ji+1p=ija[p]>rl+1p=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;
}
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值