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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章