1. 程式人生 > >51Nod 1461 - 穩定桌(列舉+線段樹)

51Nod 1461 - 穩定桌(列舉+線段樹)

【題目描述】
在這裡插入圖片描述
【思路】
線段樹還是寫的少,不知道還有這樣子的用法
看了別人的題解,這道題是要列舉所有的長度,然後計算把當前長度作為最長桌腿時消耗的最小代價,取最小值就是答案. 當列舉某個長度 i i 時,要把比 i i

長的桌腿都刪掉,這個可以用代價的字首和處理. 然後看一看現在長度為 i i 的桌腿有沒有超過剩下桌腿的一半,超過了直接更新答案,如果沒超過那麼還要刪掉一定數量的長度比 i i 小的桌腿,這裡用一個線段樹來作為一個集合儲存長度 <
i <i
的桌腿的數量以及代價和,線段樹的區間表示的意義就是代價,維護的是一段代價區間總和,即桌腿在這個代價區間裡的總數量和總代價和,這樣可以用查詢操作查出代價前 k k 小的桌腿的代價總和

#include<bits/stdc++.h>
#define max(a,b)(a>b?a:b)
#define min(a,b)(a<b?a:b)
#define node tree[id]
#define lson tree[id<<1]
#define rson tree[id<<1|1]
using namespace std;
typedef long long ll;
const ll inf=1e18;
const int maxn=1e5+50;

struct Tree{
	int left,right;
	int num;
	ll sum;
};

int n;
int a[maxn],b[maxn],maxa,maxb;\
vector<int> g[maxn];//g[i]記錄長度為i的桌腿代價分別為多少
ll c[maxn],s[maxn];//c[i]是數量字首和,s[i]是代價字首和
Tree tree[maxn<<2];

void pushup(int id){
	node.num=lson.num+rson.num;
	node.sum=lson.sum+rson.sum;
}

void build(int id,int le,int ri){
	node.left=le;
	node.right=ri;
	node.num=0;
	node.sum=0;
	if(le==ri) return;
	int mid=(le+ri)>>1;
	build(id<<1,le,mid);
	build(id<<1|1,mid+1,ri);
}

void update(int id,int d){//增加一個代價為d的桌腿
	if(node.left==node.right){
		++node.num;
		node.sum+=d;
		return;
	}
	int mid=(node.left+node.right)>>1;
	if(d<=mid) update(id<<1,d);
	else update(id<<1|1,d);
	pushup(id);
}

ll query(int id,int k){//查出當前線段樹中代價前k小的桌腿的代價總和
	if(node.left==node.right){
		return 1LL*node.left*k;
	}
	int mid=(node.left+node.right)>>1;
	if(lson.num==k)
		return lson.sum;
	else if(lson.num<k)
		return lson.sum+query(id<<1|1,k-lson.num);
	else
		return query(id<<1,k);
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		maxa=max(maxa,a[i]);
		++c[a[i]];
	}
	for(int i=1;i<=n;++i){
		scanf("%d",&b[i]);
		maxb=max(maxb,b[i]);
		s[a[i]]+=b[i];
		g[a[i]].push_back(b[i]);
	}
	for(int i=1;i<=maxa;++i){
		c[i]+=c[i-1];
		s[i]+=s[i-1];
	}
	build(1,1,maxb);
	ll ans=inf;
	for(int i=1;i<=maxa;++i){//列舉所有長度,計算答案,取最小值
		ll tmp=s[maxa]-s[i];
		if(2*g[i].size()>c[i]){//數目足夠多,不用刪比它小的
			ans=min(ans,tmp);
		}
		else{//需要刪k個
			int k=c[i]-2*g[i].size()+1;
			ans=min(ans,tmp+query(1,k));
		}
		for(int j=0;j<g[i].size();++j){//將當前長度的桌腿加入線段樹
			update(1,g[i][j]);
		}
	}
	printf("%lld\n",ans);
	return 0;
}