专题突破之反悔贪心——建筑抢修,Cow Coupons G, Voting (Hard Version),Cardboard Box

[JSOI2007]建筑抢修

luogu4053

将建筑按照结束时间从小到大排序

然后记录一下已经修理的建筑总共的花费时间

如果花费时间加上现在这个建筑的修建时间超过了这个建筑的结束时间

就考虑反悔最长修建时间的建筑(必须要比现在的这个建筑修建时间长才行)

用大根堆维护即可

如果没有比现在这个建筑更长时间的已修建的建筑,那么这个建筑就无法被修建

这个贪心的原理是:建筑之间没有权值,换言之修建任何一个建筑的收益是等价的,那么排序后就尽可能地腾出更多的空闲时间给后面的建筑修建

#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
#define maxn 150005
struct node { int ti, lim; }v[maxn];
priority_queue < int > q;
int n;

signed main() {
	scanf( "%lld", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lld %lld", &v[i].ti, &v[i].lim );
	sort( v + 1, v + n + 1, []( node x, node y ) { return x.lim < y.lim; } );
	int now = 0, ans = 0;
	for( int i = 1;i <= n;i ++ ) {
		if( now + v[i].ti <= v[i].lim )
			now += v[i].ti, q.push( v[i].ti ), ans ++;
		else {
			if( ! q.empty() and q.top() > v[i].ti )
				now -= q.top(), now += v[i].ti, q.pop(), q.push( v[i].ti );
		}
	}
	printf( "%lld\n", ans );
	return 0;
}

[USACO12FEB]Cow Coupons G

luogu3045

按每头牛的打折后的c从小到大排序

考虑前 k k k头牛肯定是都把优惠券用了,然后进入反悔堆,p-c

而后考虑两种情况

  • 不用优惠券,就是原价购买,这需要对第 k k k头牛后面的所有牛进行原价的排序,用小根堆维护
  • 用优惠券,反悔一张效果最差的(也就是p-c最小的)加上这头牛打折后的价格,就是反悔后的花费

两个取较小值与现在的金额比较即可

#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 500005
#define int long long
#define Pair pair < int, int >
#define inf 1e18
priority_queue < int, vector < int >, greater < int > > q;
priority_queue < Pair, vector < Pair >, greater < Pair > > q1, q2;
struct node { int p, c; }cow[maxn];
int n, m, k;
bool vis[maxn];

signed main() {
	scanf( "%lld %lld %lld", &n, &k, &m );	
	for( int i = 1;i <= n;i ++ ) scanf( "%lld %lld", &cow[i].p, &cow[i].c );
	sort( cow + 1, cow + n + 1, []( node x, node y ) { return x.c < y.c; } );
	for( int i = 1;i <= k;i ++ ) {
		if( cow[i].c <= m ) m -= cow[i].c, q.push( cow[i].p - cow[i].c );
		else return ! printf( "%lld\n", i - 1 );
	}
	if( k == n ) return ! printf( "%lld\n", n );
	int ans = k;
	for( int i = k + 1;i <= n;i ++ ) {
		q1.push( make_pair( cow[i].c, i ) );
		q2.push( make_pair( cow[i].p, i ) );
	}
	q.push( inf );
	q1.push( make_pair( inf, 0 ) );
	q2.push( make_pair( inf, 0 ) );
	while( 1 ) {
		while( vis[q1.top().second] ) q1.pop(); 
		while( vis[q2.top().second] ) q2.pop();
		int i1 = q1.top().second;
		int i2 = q2.top().second;
		int w1 = q.top() + q1.top().first;
		int w2 = q2.top().first;
		if( min( w1, w2 ) > m ) break;
		ans ++; m -= min( w1, w2 );
		if( w1 < w2 )
			q.pop(), q1.pop(), vis[i1] = 1, q.push( cow[i1].p - cow[i1].c );
		else
			q2.pop(), vis[i2] = 1;
	}
	printf( "%lld\n", ans );
	return 0;
}

CF1251E2 Voting (Hard Version)

CF1251E2

如果将 m m m的限制变成时间限制,第 i i i个人的投票必须在 n − m i − 1 n-m_i-1 nmi1的时间以前(包括这个时刻)

注意:是以0时刻开始算起的

投票,否则就会产生 p i p_i pi的罚款

这就巧妙地转化成了贪心经典——不守交规

按照截止时间排序,每个人投票需要一个时间

  • 如果时间还有剩,这个人就可以不被罚
  • 如果没有剩,那么往前面找最小的罚金(必须比现在的罚金小)
    • 有就选择交换这两个人的投票时间,将这个人入反悔堆,罚金是不可避免的,只不过变成了交最小的罚金
    • 没有就老老实实让这个人交罚金
#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
#define maxn 200005
struct node { int p, m; }vote[maxn];
priority_queue < int, vector < int >, greater < int > > q;
int T, n;

signed main() {
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld", &n );
		for( int i = 1;i <= n;i ++ ) {
			scanf( "%lld %lld", &vote[i].m, &vote[i].p );
			vote[i].m = n - vote[i].m - 1;
		}
		sort( vote + 1, vote + n + 1, []( node x, node y ) { return x.m < y.m; } );
		while( ! q.empty() ) q.pop();
		int ans = 0;
		for( int i = 1;i <= n;i ++ )
			if( q.size() <= vote[i].m )	
				q.push( vote[i].p );
			else
				if( ! q.empty() and q.top() < vote[i].p )
					ans += q.top(), q.pop(), q.push( vote[i].p );
				else
					ans += vote[i].p;
		printf( "%lld\n", ans );
	}
	return 0;
}

CF436E Cardboard Box

CF436E

  • 贪心操作1:选择激活某个关卡的第一颗星 花费为最小的 a i ai ai
  • 贪心操作2:选择激活某个关卡的第二颗星 花费为最小的 b j − a j bj-aj bjaj
  • 反悔操作1:反悔最大花费的只激活了第一颗星的关卡 直接将花费最小的未激活关卡激活完2颗星 花费为 b i − a j bi-aj biaj
  • 反悔操作2:反悔最大花费的2颗星全激活的关卡 直接将花费最小的未激活关卡2颗星激活完 花费为 b i − ( b j − a j ) bi-(bj-aj) bi(bjaj)

直接开五个堆维护,代码里有详细注释

#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define Pair pair < int, int >
#define int long long
#define maxn 300005
#define inf 1e18
priority_queue < Pair, vector < Pair >, greater < Pair > > q1, q2, q3;
priority_queue < Pair > q4, q5;
int n, m;
int ans[maxn], a[maxn], b[maxn];

/*
q1: 关卡激活为0颗星的a
	贪心策略1:选择花费最小的还是0颗星的关卡激活成1颗星
	小根堆 
q2: 关卡激活为1颗星的b-a
	贪心策略2:选择花费最小的激活1颗星的关卡激活成2颗星
	小根堆 
q3: 关卡激活为0颗星的b
	反悔策略12:回收一颗星后直接激活最小花费某个关卡的两颗星
	小根堆
q4: 关卡激活为1颗星的a
	反悔策略1:反悔最大花费的激活1颗星的关卡操作变成激活为0
	大根堆 
q5: 关卡激活为2颗星的b-a
	返回策略2:反悔最大花费的激活2颗星的关卡操作变成只激活1
	大根堆 
*/

void add0( int x ) {
	ans[x] = 0;
	q1.push( make_pair( a[x], x ) );
	q3.push( make_pair( b[x], x ) );
}

void add1( int x ) {
	ans[x] = 1;
	q2.push( make_pair( b[x] - a[x], x ));
	q4.push( make_pair( a[x], x ) );
}

void add2( int x ) {
	ans[x] = 2;
	q5.push( make_pair( b[x] - a[x], x ) );
}

signed main() {
	scanf( "%lld %lld", &n, &m );
	q1.push( make_pair(  inf, n + 1 ) );
	q2.push( make_pair(  inf, n + 1 ) );
	q3.push( make_pair(  inf, n + 1 ) );
	q4.push( make_pair( -inf, n + 1 ) );
	q5.push( make_pair( -inf, n + 1 ) ); 
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%lld %lld", &a[i], &b[i] );
		add0( i );
	}
	int ret = 0;
	for( int i = 1;i <= m;i ++ ) {
		int i1 = q1.top().second;
		int i2 = q2.top().second;
		int i3 = q3.top().second;
		int i4 = q4.top().second;
		int i5 = q5.top().second;
		while( q1.size() > 1 and ans[i1] ^ 0 ) q1.pop(), i1 = q1.top().second;
		while( q2.size() > 1 and ans[i2] ^ 1 ) q2.pop(), i2 = q2.top().second;
		while( q3.size() > 1 and ans[i3] ^ 0 ) q3.pop(), i3 = q3.top().second;
		while( q4.size() > 1 and ans[i4] ^ 1 ) q4.pop(), i4 = q4.top().second;
		while( q5.size() > 1 and ans[i5] ^ 2 ) q5.pop(), i5 = q5.top().second;
		int w1 = q1.top().first;
		int w2 = q2.top().first;
		int w3 = q3.top().first;
		int w4 = q4.top().first;
		int w5 = q5.top().first;
		//i:表示未激活的关卡 j:表示已激活的关卡 
		int t1 = w1; 			//贪心操作1:选择激活某个关卡的第一颗星 花费为最小的ai
		int t2 = w2; 			//贪心操作2:选择激活某个关卡的第二颗星 花费为最小的bj-aj
		int t3 = w3 - w4; 		//反悔操作1:反悔最大花费的只激活了第一颗星的关卡 直接将花费最小的未激活关卡激活完2颗星  花费为bi-aj 
		int t4 = w3 - w5;		//反悔操作2:反悔最大花费的2颗星全激活的关卡 直接将花费最小的未激活关卡2颗星激活完	花费为bi-(bj-aj) 
		int Min = min( min( t1, t2 ), min( t3, t4 ) );
		ret += Min;
		if( Min == t1 ) q1.pop(), add1( i1 );
		else if( Min == t2 ) q2.pop(), add2( i2 );
		else if( Min == t3 ) q3.pop(), q4.pop(), add2( i3 ), add0( i4 );
		else q3.pop(), q5.pop(), add2( i3 ), add1( i5 );
	}
	printf( "%lld\n", ret );
	for( int i = 1;i <= n;i ++ ) printf( "%lld", ans[i] );
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值