BZOJ1029 [JSOI2007]建筑抢修(贪心+堆)

【题解】

仅按T1或T2从小到大修理显然是不正确的 


可以发现:对于某个建筑集合,若它们都能被抢修,则按T2从小到大的顺序修理是最优的 

证明:(相邻交换法)
对于建筑物(a1,b1)与(a2,b2),(a,b)代表(T1,T2)且T前+a<=b,设b1<=b2,可得下表:

                  实际修理时间    允许时间                          实际修理时间    允许时间 
1号在前:T前+a1              b1                  2号在前:T前+a2               b2
                  T前+a1+a2       b2                                    T前+a1+a2         b1
        
若 2号在前时,1、2号都能修,则 T前+a1+a2 <= b1 <= b2,因此 1号在前时也能修两个 
若 1号在前时,1、2号都能修,则只知道 T前+a1+a2 <= b2,不能保证 2号在前时也能修两个 

(也可用此方法尝试证明 能否按T1从小到大的顺序修理 结果是无法证明)
结论:按T2乱序能全修的,按T2升序也必能全修; 反之则不一定 
因此 按T2从小到大的顺序修理是最优的 

产生贪心算法:
先按T2从小到大排序,从前往后决策 
对于i号,若 a总 <= bi,则修复 
否则,找出之前修复过的建筑中 耗时(a)最大的j号,若aj>ai(保证替换后结果更优),则不修j,改为修i

用大根堆找出 这个j号即可 


#include<stdio.h>
#include<stdlib.h>
int a[150005]={0},b[150005]={0},heap[150005]={0};
int p=0;
void jh(int* a,int* b)
{
	int t=*a;
	*a=*b;
	*b=t;
}
void kp(int low,int high)
{
	int i=low,j=high,mid=b[(i+j)/2];
	while(i<j)
	{
		while(b[i]<mid) i++;
		while(b[j]>mid) j--;
		if(i<=j)
		{
			jh(&a[i],&a[j]);
			jh(&b[i],&b[j]);
			i++;
			j--;
		}
	}
	if(j>low) kp(low,j);
	if(i<high) kp(i,high);
}
void tj(int x)//添加元素 
{
	int i;
	heap[++p]=x;
	for(i=p;i!=1;i/=2)
	{
		if(heap[i]>heap[i/2]) jh(&heap[i],&heap[i/2]);
		else return;
	}
}
void sc()//删除堆顶 
{
	int i=1;
	heap[1]=heap[p--];
	while(i*2<=p)
	{
		i*=2;
		if(i+1<=p&&heap[i]<heap[i+1]) i++;
		if(heap[i]>heap[i/2]) jh(&heap[i],&heap[i/2]);
		else return;
	}
}
int main()
{
	int n,i,t=0,ans=0;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
		scanf("%d%d",&a[i],&b[i]);
	kp(1,n);
	for(i=1;i<=n;i++)
	{
		if(t+a[i]<=b[i])
		{
			t+=a[i];
			tj(a[i]);
			ans++;
		}
		else
			if(p>0&&heap[1]>a[i])
			{
				t=t-heap[1]+a[i];
				sc();
				tj(a[i]);
			}
	}
	printf("%d",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值