1. 程式人生 > >二叉堆_連結串列_CH1812_生日禮物

二叉堆_連結串列_CH1812_生日禮物

點此開啟題目頁面

思路分析:

    設輸入序列為A[1...n], 將A的首尾的連續的非正數去除的序列B[1...m], 將序列B所有相鄰的正數合併, 所有相鄰的非正數合併.得到序列C[1...p], C[1...p]為正, 非正交替的序列且C[1] > 0, C[p] > 0, C[1...p]中正數的個數大於M, 設C[i]為C[1...p]中絕對值最小的數, 那麼有如下結論1成立:

    結論1: 如果i = 1或i = p那麼一定存在不包含C[i]和與C[i]相鄰的元素的最優選擇方案(所選則的至多M個連續的部分的和最大的選擇方案, 根據C[i]的絕對值最小, 且與C[i]相鄰的元素必為絕對值不小於C[i]的負數較易證明此結論)

    結論2: 如果1 < i < p, 那麼存在最優的選擇方案同時包含C[i - 1], C[i], C[i + 1]

    證明:

    (1)當C[i] > 0時, 假設存在最優方案R只包含三者中的一個, 顯然不可能只包含C[i - 1](負數)或C[i + 1](負數), 即只包含C[i], 由於C中正數的個數大於M, 故可使用任意未被選擇的整數C[j]替換C[i], 替換之後所有選擇的數的和不會減小.假設存在最優方案R只包含三者中的兩個, 顯然不能為C[i - 1]和C[i + 1], 不妨設包含C[i]和C[i + 1], 此時去掉C[i]和C[i + 1]剩餘選擇的元素之和不會減小. 故C[i] > 0時, 存在最優方案同時包含C[i - 1], C[i]和C[i + 1]或同時不包含.

    (2)當C[i] <= 0時, 假設存在最優方案R只包含三者中的一個, 顯然不可能只包含C[i], 不妨設包含C[i - 1], 此時將C[i]和C[i - 1]包含進來, 同時選擇的元素之和不會減小, 假設最優方案R只包含三者中的兩個, 顯然不可能包含C[i - 1]和C[i]或C[i + 1]和C[i], 即只能包含C[i - 1]和C[i + 1], 此時可包含進C[i]使得選擇的連續的部分個數減少1, 因此可在C中選擇任意一個之前R中未被選擇的正數, 且所選擇的元素之和不會減小, 故C[i] <= 0時, 存在最優方案同時包含C[i - 1], C[i]和C[i + 1]或同時不包含.

    基於結論2, 可不斷減少序列中正數的個數, 直至正數的個數不超過M(此時最優方案選擇所有的正數), 下面是基於此思想的AC程式碼:

//CH1812_生日禮物
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define fi first
#define se second
#define mp make_pair
using namespace std; 
const int MAX = 1e5 + 5;
int A[MAX], N, M;
pair<int, int> li[MAX << 1]; int head, tot, nex[MAX << 1], pre[MAX << 1];
pair<int, int> he[MAX]; int size;
//he[a] < he[b]時返回true, 否則返回false 
bool cmp(int a, int b){
	return abs(he[a].fi) < abs(he[b].fi);
}
//交換he[a]和he[b], 同時交換在li中對應指標
void hswap(int a, int b){
	swap(li[he[a].se].se, li[he[b].se].se), swap(he[a], he[b]);
} 
void up(int a){
	for(int q = a, p = q >> 1; p && cmp(q, p); hswap(q, p), q = p, p = q >> 1);
}
void down(int a){
	for(int p = a, l = p << 1, r = l + 1, t
		; l <= size && (cmp(l, p) || r <= size && cmp(r, p))
		; t = r > size || min(l, r, cmp) == l? l: r, hswap(p, t), p = t, l = p << 1, r = l + 1);	 
}
void hins(pair<int, int> pa){
	he[++size] = pa, up(size);
}
void hdel(int a){
	hswap(a, size--); if(a != size + 1) up(a), down(a);
}
//在li[a]之後插入b, a為0表示在表頭之前插入
void lins(int a, pair<int, int> b){
	li[++tot] = b;
	if(!a) nex[tot] = head, pre[head] = tot, head = tot;
	else nex[tot] = nex[a], pre[tot] = a, pre[nex[a]] = tot, nex[a] = tot;	
} 
void ldel(int a){
	if(a == head) head = nex[head], pre[head] = 0;
	else nex[pre[a]] = nex[a], pre[nex[a]] = pre[a];
}
int main(){
	scanf("%d %d", &N, &M);
	for(int i = 1; i <= N; ++i) scanf("%d", &A[i]);
	//去除A首尾連續的非正數
	int be = 1, end = N; 
	while(be <= end && A[be] <= 0) ++be; while(end >= be && A[end] <= 0) --end;
	if(be > end){
		cout << "0" << endl; return 0;
	}
	//合併相鄰的正數, 非正數
	A[1] = A[be]; int len = 1; 
	for(int i = be + 1; i <= end; ++i) 
		if(A[i - 1] > 0 && A[i] > 0 || A[i - 1] <= 0 && A[i] <= 0) A[len] += A[i]; else A[++len] = A[i];
	//將A[1..len]插入li
	for(int i = 1; i <= len; ++i) li[i].fi = A[i], nex[i] = i + 1, pre[i] = i - 1;
	nex[len] = 0, pre[1] = 0, head = 1, tot = len;
	//將A[1...len]插入堆he中
	for(int i = 1; i <= len; ++i) li[i].se = size + 1, hins(mp(li[i].fi, i)); 
	while(size / 2 + 1 > M){
		int u = he[1].fi, v = he[1].se;
		if(!pre[v]) hdel(li[nex[v]].se), hdel(1), ldel(nex[v]), ldel(v);
		else if(!nex[v]) hdel(li[pre[v]].se), hdel(1), ldel(pre[v]), ldel(v);
		else{
			int t = u + li[pre[v]].fi + li[nex[v]].fi, pp = pre[pre[v]];
			hdel(li[pre[v]].se), hdel(li[nex[v]].se), hdel(1)
			, ldel(pre[v]), ldel(nex[v]), ldel(v)
			, lins(pp, mp(t, size + 1)), hins(mp(t, tot)); 
		}		
	}
	int res = 0; for(int i = head; i; i = nex[i]) if(li[i].fi > 0) res += li[i].fi;
	cout << res << endl;
	return 0;
}