51Nod 1461 - 稳定桌(枚举+线段树)

【题目描述】
在这里插入图片描述
【思路】
线段树还是写的少,不知道还有这样子的用法
看了别人的题解,这道题是要枚举所有的长度,然后计算把当前长度作为最长桌腿时消耗的最小代价,取最小值就是答案. 当枚举某个长度 i i i 时,要把比 i i i 长的桌腿都删掉,这个可以用代价的前缀和处理. 然后看一看现在长度为 i i i 的桌腿有没有超过剩下桌腿的一半,超过了直接更新答案,如果没超过那么还要删掉一定数量的长度比 i i i 小的桌腿,这里用一个线段树来作为一个集合存储长度 &lt; i &lt;i <i 的桌腿的数量以及代价和,线段树的区间表示的意义就是代价,维护的是一段代价区间总和,即桌腿在这个代价区间里的总数量和总代价和,这样可以用查询操作查出代价前 k 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值