堆+雙向鏈表 - 種樹(BZOJ2151)

學習ing

Analysis

第一眼反應這不是個水dp嗎……
仔細一想,是我太naive了
由於是個環,所以這個是有後效性的(1位置選不選會影響n位置)

那怎麼辦呢?

題解爸爸告訴我:
如果沒有相鄰不能選的限制,就直接貪心選擇前m大的值即可
以後做題的時候先從簡單的思考起走,把限制去掉
那麼現在加上這個限制,我們該如何考慮呢?
沿用上面的貪心思路
假設A[3]最大,那我們就試圖去選A[3]。選中之後首先要去掉3,並且,A[2]和A[4]也都不能選了,所以將它們刪掉——

但是慢着!這可能會導致問題。假設A[3]=20,A[2]=A[4]=19,那麼同時選A[2],A[4]可能比選A[3]要優!在最後的方案中可能是A[2]+A[4]而非A[3]。這種情況要怎麼解決呢?

可以發現一點:由於A[3]最大,所以在最後的方案中,不可能只選A[2],A[4]中的一個。
原因很簡單:假設在最優方案中選了A[2]但未選A[4],那可以簡單地把A[2]換成A[3],由於未選A[4],所以這樣不會產生任何矛盾,並且把A[2]換成A[3]後,總的美觀度不會下降。

因此,我們先去掉2,3,4,然後加入一個新的“物品”,其權值爲A[2]+A[4]-A[3],代表同時選2,4,刪去3.這樣,在選了3之後再選這個新物品,功效就相當於剛纔所說的,把A[3]換成A[2]+A[4]。

這個新物品應該放在哪裏呢?它的含義是“選2,4”,所以很容易想到,應該把它放在1,5中間。
出於方便起見,不妨在刪掉2,4後直接把A[3]改成A[2]+A[4]-A[3],顯然這個位置是正確的。

如此就將N個物品,需要選M個的問題轉化成了在N-1個物品中選的問題。並且可以發現一個很好的性質:新的3所對應的仍然是“選中物品數+1”!(把選3換成了選2,4,即多選了一個物品)

也就是說,完全可以把新的3看做一個和1,5毫無區別的物品,現在我們只需要在1,3,5三個物品中選擇M-1個!如此下去,直到選擇M次,就可以得到答案。

因此描述一下算法:以A[i]爲關鍵字建大根堆,用一個鏈表存放當前物品。
最初鏈表中元素是1~N,i的後繼是i+1,前驅是i-1(當然,1的前驅是N,N的後繼是1)。
執行M次操作,每一次操作都將堆頂元素k取出,ans+=A[k]。然後在鏈表中刪除k的前驅pre和後繼nxt,令A[k]=A[pre]+A[nxt]-A[k],並更新堆。

這個算法運行的很好,但你可能感覺有點虛——爲什麼每次選A值最大的就正確呢?
可以發現,在上面的討論中“選3”時,我們實際上做的是聲明如下事實:
在最終答案中要麼選了3,要麼同時選了2,4.換句話說,要麼選了3,要麼在此基礎上選了A[2]+A[4]-A[3]。

所以我們實際上是重寫了這個問題,將其變成“N-2個物品中選M-1”個的形式,如此一直化歸,直到最後變成“N-2(M-1)個物品中選1個”,這時答案就是顯然的。


Code
#include<bits/stdc++.h>
#define re register
#define in read()
using namespace std;
inline char nc(){
	static char buf[100000],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
	char ch;int f=1,res=0;
	while((ch=nc())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<1)+(res<<3)+(ch^48);
		ch=nc();
	}
	return f==1?res:-res;
}
const int N=2e5+10;
bool vis[N];
int a[N],n,m;
int pre[N],suf[N];
struct node{
	int val,id;
	bool operator <(const node &a)const{return val<a.val;}
};
priority_queue<node> q;
int main(){
	n=in;m=in;
	if(m>n/2) {printf("Error!");return 0;}
	for(re int i=1;i<=n;++i){
		a[i]=in;
		q.push((node){a[i],i});
		pre[i]=i-1;suf[i]=i+1;
	} 
	int cnt=0,ans=0;
	pre[1]=n;suf[n]=1;
	while(cnt<m){
		while(vis[q.top().id]) q.pop();
		node tmp=q.top();q.pop();
		int i=tmp.id;
		ans+=a[i];
		a[i]=a[pre[i]]+a[suf[i]]-a[i];
		vis[pre[i]]=1;vis[suf[i]]=1;
		q.push((node){a[i],i});
		++cnt;
		pre[i]=pre[pre[i]];suf[pre[i]]=i;
		suf[i]=suf[suf[i]];pre[suf[i]]=i;
	}
	printf("%d",ans);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章