贪心基础练习题

贪心算法
1、今年暑假不AC HDU - 2037
题意:给出一些区间,找出最大的不会相互覆盖的最大区间数
思路:按照区间的终点从小到大排序,从开始扫描一遍,如果出现上一个区间的终点,小于下一个区间的终点的情况,ans++

struct Node 
{
	int s,e;
	
	bool operator<(const Node &b) const
	{
		return e<b.e;
	} 
	
}node[110];


int main()
{
  
    int n;
    while(scanf("%d",&n)&&n)
	{
		for(int i=1;i<=n;++i)
			scanf("%d %d",&node[i].s,&node[i].e);
		sort(node+1,node+1+n);
		
		int now=node[1].e,ans=1;
		for(int i=2;i<=n;++i)
		{
			if(now<=node[i].s)
			{
				ans++;
				now=node[i].e;
			}
		}
		printf("%d\n",ans);
	} 
    
	return 0;
}

2、Radar Installation POJ - 1328

题意:放置雷达,问最少需要多少雷达能覆盖所有岛屿
思路:将坐标化为区间,按起点或者终点排序,维护一个前面区间的最短结束长度minEnd。当一个区间的起点>minEnd时,显然需要重新放置一个雷达

struct Node
{
	double s,e;
	bool operator<(const Node &b) const
	{
		return s<b.s;
	}
}node[1010];


int main()
{
    int n,Cas=0;
    int d;
    while(scanf("%d %d",&n,&d)&&(n||d))
    {
		int flag=0;
    	double x,y;
    	for(int i=1;i<=n;++i)
    	{
    		scanf("%lf %lf",&x,&y);
    		
	    	if(d<0||y>d)
				flag=1; 		
    		  		
    		double z=d*d-y*y; 
    		node[i].s=x-pow(z,0.5);
    		node[i].e=x+pow(z,0.5);
    	}
	    sort(node+1,node+1+n);
	    	
	    double minEnd=node[1].e;
		int ans=0;
	   	for(int i=2;i<=n;++i)
	   	{
	   		if(node[i].s>minEnd)
	   		{
	   			ans++;
				minEnd=node[i].e;    	
	   		}
	   		else
	   			minEnd=min(minEnd,node[i].e);
	   	} 
		if(flag==1)
		{
			printf("Case %d: -1\n",++Cas);    
			continue;			
		}
		else  	
	   		printf("Case %d: %d\n",++Cas,++ans);
	}
	return 0;
}
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;

struct Node
{
    double l,r;
    bool operator<(const Node &b) const
    {
        return r<b.r;
    }
} p[1010];

int main()
{
    int n,d,Case=0;
    while(scanf("%d%d",&n,&d)&&(n||d))
    {
        int f=0;
        double x,y;
        for(int i=1; i<=n; ++i)
        {
            scanf("%lf%lf",&x,&y);
            if(d<0||y>d) f=1;
            double z=d*d-y*y;
            p[i].l=x-pow(z,0.5);
            p[i].r=x+pow(z,0.5);
        }
        sort(p+1,p+1+n);
        int ans=1;
        double now=p[1].r;
        for(int i=2; i<=n; ++i)
            if(now<p[i].l) ans++,now=p[i].r;
        if(f==1) printf("Case %d: -1\n",++Case);
        else printf("Case %d: %d\n",++Case,ans);
    }
    return 0;
}

3、Stall Reservations POJ - 3190
题意:有N头奶牛挤奶,它们的区间不能够重叠,端点也不能重叠。要为它们分配畜栏,问最少分配几个畜栏,以及每头牛在畜栏的编号
思路:将所有的奶牛按开始挤奶的时间,从小到大排序。没有畜栏时,分配一个畜栏。如果存在畜栏,就判断已有畜栏中挤奶结束最早的是否 > 当前奶牛的开始时间,大于,则重开一个畜栏。小于,则 更新这个畜栏的结束时间

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <queue>
using namespace std;

int n;
struct Node
{
    int t,id;
    bool operator<(const Node &b) const
    {
        return t>b.t;
    }
};

struct Cow
{
    int l,r,id;
    bool operator<(const Cow &b) const
    {
        return l<b.l;
    }
} p[50010];

int ans[50010];

int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; ++i)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        p[i].l=l;
        p[i].r=r;
        p[i].id=i;
    }
    sort(p+1,p+1+n);
    priority_queue<Node> pq;
    int tot=0;
    for(int i=1; i<=n; ++i)
    {
        if(!pq.empty()&&pq.top().t<p[i].l)
        {
            Node u=pq.top();
            pq.pop();
            ans[p[i].id]=u.id;
            u.t=p[i].r;
            pq.push(u);
        }
        else
        {
            tot++;
            ans[p[i].id]=tot;
            Node u;
            u.id=tot;
            u.t=p[i].r;
            pq.push(u);
        }
    }
    printf("%d\n",tot);
    for(int i=1; i<=n; ++i) printf("%d\n",ans[i]);
    return 0;
}

4、Gone Fishing POJ - 1042
题意:一个人去钓鱼,有 n n n 个湖,他可以从1号湖走带n号湖。走路时间为 t i t_i ti ,每个湖能钓到的鱼为 f i f_i fi ,每隔 5 分钟,在第 i i i 个湖,能够钓到的鱼减少 d i d_i di 。问能够钓到的最多的鱼,和在每个湖钓鱼停留的时间。如果说钓鱼数量相同,就输出在第一个湖停留时间最长的方案

难点:我们不知道他最终会走到哪个湖,会在某个湖停留多久
思路:我们可以枚举到达的终点。这样就可以去掉走路的时间t,剩余的时间都是用于钓鱼。那么钓鱼,就可以选择最佳的钓鱼方案。然后在枚举的各个方案中取一个最佳方案,就是我们最终的答案

具体实现:用结构体Lake存放No和当前能够钓的鱼f[i]。在优先队列中,选择当前能够钓到的最多的鱼,即队首。

const int maxn=30,INF=0x3f3f3f3f;
const int mod=1e9+7;
int n,h,f[maxn],d[maxn],t[maxn]; 
int nowTime[maxn],bestTime[maxn];

struct Lake
{
	int No,f;
	bool operator <(const Lake&b) const
	{
		if(f==b.f)
			return No>b.No;
		return f<b.f;
	}
	Lake(int i,int f1):No(i),f(f1)
	{		
	}
};
int main()
{    
    while(scanf("%d",&n)&&n)
    {
    	scanf("%d",&h);
    	rep(i,1,n)
    		scanf("%d",&f[i]);
    	rep(i,1,n)
    		scanf("%d",&d[i]);
    	rep(i,1,n-1)
    		scanf("%d",&t[i]);
    			
    	//一开始设置成了0,WA了一次,应该是存在一组数据是钓到0条鱼的情况
    	int maxFish=-INF;
    	
    	for(int i=1;i<=n;++i)
    	{   		
    		int workTime=h*60/5;
    		for(int j=0;j<i;++j)
    			workTime-=t[j];
    		
    		priority_queue<Lake> pq;
	    	mes(nowTime,0);

    		
    		for(int j=1;j<=i;++j)
    			pq.push(Lake(j,f[j]));
    		
    		int total=0;
    		
    		for(int j=1;j<=workTime;++j)
    		{
    			Lake lake=pq.top();
    			pq.pop();
    			
    			total+=lake.f;
    			nowTime[lake.No]++;
    			
    			if(lake.f>=d[lake.No])
    				lake.f-=d[lake.No];
    			else
    				lake.f=0;
    			pq.push(lake);
    		}
    		if(total>maxFish)
    		{
    			maxFish=total;
    			memcpy(bestTime,nowTime,sizeof(nowTime));
    		}
    		else if(total==maxFish)
    		{
    			for(int j=1;j<=n;++j)
    			{
    				if(bestTime[j]<nowTime[j])
    				{
    					memcpy(bestTime,nowTime,sizeof(nowTime));
    					break;
    				}
    				else if(bestTime[j]>nowTime[j])
    					break;
    			}
    		}
	
    	}
    	
    	for(int i=1;i<n;++i)
    		printf("%d, ",bestTime[i]*5);
    	printf("%d\n",bestTime[n]*5);	
    	printf("Number of fish expected: %d\n\n",maxFish);	
    }  
	return 0;
}

5、Buildings HDU - 4296
题意:要求一种排列的方式,使得所有层里面最大的PAD最小
思路:虽然是道水题,但我感觉排序的方式还是挺巧妙的。
①对于相邻放置的木板i,j。先假设i在j的上面,它们上面重量为sum
则,a=sum-si b=sum+wi-sj
②交换两个板的位置之后
a’=sum+wj-si
b’=sum-sj
③假设第一种方案优于第二种方案,那么max ( a , b ) < max ( a’ , b’ )
也就是sum+wj-si>sum+wi-sj 也就是wi+si<wj+sj

贪心策略的确定基本可以有两个想法,一个是全自己想,然后自己出数据,
不断的使自己的策略接近正确答案。二是可以先搞少的数据,两个或三个,
列公式确定贪心策略
坑点:数据用ll

const int maxn=1e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;

struct Node
{
	int w,s;
	bool operator<(const Node &b) const
	{
		return w+s<b.w+b.s;
	}
}node[maxn]; 


int main()
{

    int n;
    while(~scanf("%d",&n))
    {
    	ll sum=0;
    	for(int i=1;i<=n;++i)
    		scanf("%d %d",&node[i].w,&node[i].s);
    	
    	sort(node+1,node+1+n);
    	ll Max=-INF;
    	for(int i=1;i<=n;++i)
    	{    		   		
    		if(Max<sum-node[i].s)
    			Max=sum-node[i].s;
    		sum+=node[i].w; 	
    	}
    	printf("%lld\n",Max);
    	
    }
    
   
	return 0;
}

6、Sunscreen POJ - 3614
题意:防晒霜的spf在一定范围内对奶牛起作用,每头牛的范围不同, 问你最多能给几头奶牛使用有效的防晒霜

难点:将奶牛按起点排序后,上界却不知道。假如有一头牛的范围是1–8,另一头牛的范围是4-6,有5和7两瓶防晒霜。此时,不可以将5给第一头牛使用,因为把5给了第一头牛。7就无法给第二头牛使用
思路:正确的贪心策略应该是,按起点从小到大排序,然后用优先队列取上界最小的牛来使用spf小的防晒霜。因为,上界越大,那么它可以使用的防晒霜就有更多的可能。
反过来贪心也是一样的。

具体实现:遍历的时候,有两种方式,遍历牛或者是遍历lotion,但显然牛的上界需要放入优先队列中维护。所以,直接遍历lotion,用pos来控制牛的遍历

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;

const int maxn=2550;
pair<int,int> cows[maxn];
class Lotion
{
	public:
		int spf,cover;
		bool operator<(const Lotion &b) const
		{
			return spf<b.spf;
		}
};
Lotion lotion[maxn];
 

int main()
{
	int C,L;
	cin>>C>>L;
	for(int i=0;i<C;i++)
		scanf("%d %d",&cows[i].first,&cows[i].second);
	for(int i=0;i<L;i++)
		scanf("%d %d",&lotion[i].spf,&lotion[i].cover);
	sort(cows,cows+C);
	sort(lotion,lotion+L);
	priority_queue <int,vector<int>,greater<int> > q;
	
	int pos=0;
	int ans=0;
	for(int i=0;i<L;i++)
	{
		while(pos<C&&cows[pos].first<=lotion[i].spf)
			q.push(cows[pos++].second);
		while(!q.empty()&&lotion[i].cover)
		{
			if(q.top()>=lotion[i].spf)
			{
				ans++;
				lotion[i].cover--;
			}
			q.pop();
		}		
	}
	cout<<ans<<endl;	
	
	return 0;
}

7、Problem Buyer HDU - 6003
题意:鸽巢原理,问你至少需要选择几个区间,才能够保证包含所有的问题难度。
思路:显然我们可以设包含这个问题的区间为x个,那么不包含这个问题的区间就是n-x个,此时我们只要在多选择一个就可以满足条件了,即n-x+1,就是单个问题的答案了。所以遍历所有问题,取最大值就可以了

具体实现:如果我们直接两重循环,暴力的话绝对会超时。此时我们考虑贪心,将问题难度c从小到大排,将区间按起点first从小到大排列,用一个pos表示当前遍历到的区间位置。将满足难度c的区间上界second,插入优先队列中。队首是最小的上界。
将上界 < 当前c[i]的弹出,即将不满足难度c [ i ]的问题弹出

复杂度:此时复杂度就从O(n^2)降到了O(n+m)或者是O(n*logn)

坑点:统计完一个答案,需要弹出一个区间,因为可能包含两个难度,在同一个区间,答案会变小

const int maxn=1e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;

pair<int,int> p[maxn];
int c[maxn];
int T,N,M; 

int main()
{
	scanf("%d",&T);
	int Cas=0;
	while(T--)
	{
		scanf("%d %d",&N,&M);
		rep(i,1,N)
			scanf("%d %d",&p[i].first,&p[i].second);
		rep(i,1,M)
			scanf("%d",&c[i]);
		
		sort(p+1,p+1+N);
		sort(c+1,c+1+M);
		
		int ans=-1,pos=1;
		
		priority_queue<int,vector<int>,greater<int> > pq; 
		
		for(int i=1;i<=M;++i)
		{
			while(pos<=N&&c[i]>=p[pos].first)
			{
				if(p[pos].second>=c[i])
					pq.push(p[pos].second);	
				pos++;			
			}

			while(!pq.empty()&&pq.top()<c[i])
				pq.pop();
			if(pq.empty())
			{
				ans=-1;
				break;
			}
			ans=max(ans,N-(int)pq.size()+1);
			//这里的pop没加,wa了一次,原因:可能有两个问题都选了以一个区间
			//比如,有4个区间不包含,那么至少需要选择5个。可实际上两个问题同时选择了一个区间的话
			//我们需要的答案应该是6个。而如果我们这里弹出一个符合的区间,那么n-(int)pq.size()+1
			//就会比原来的多1 
			pq.pop();
		} 
		
		if(ans==-1)
			printf("Case #%d: IMPOSSIBLE!\n",++Cas);
		else
			printf("Case #%d: %d\n",++Cas,ans);		
	} 
	return 0;
}

8、Copying Books POJ - 1505
题意:将M书本,分为K份,使每份总和的最大值最小
思路:最大值的求解,通过二分,单调性:mid值越大,能分出来的段数越小
二分到一定范围时,小于某个数,就会分出K+1段,而>=这个数,就能分出K段。所以一直在这个数前后波动,直到下界为1700,上界为1701,这个时候mid=1700,能分出K段,<=K,上界也变成了1700,退出循环

const int maxn=550,INF=0x3f3f3f3f;
const int mod=1e9+7;
int T,M,K;
int A[maxn]; 
int visit[maxn];

int divide(ll total)
{
	ll sum=A[1];
	int seg=1;
	for(int i=2;i<=M;++i)
	{
		if(sum+A[i]<=total)
			sum+=A[i];
		else
		{
			sum=A[i];
			seg++;
		}
	}
	return seg;
}

void outPut(int total)
{
	ll sum=A[M];
	int seg=1;
	for(int i=M-1;i>=1;--i)
	{
		if(sum+A[i]<=total&&(K-seg)<i)
			sum+=A[i];
		else
		{
			sum=A[i];
			seg++;
			visit[i]=1;
		} 
	}
	
	for(int i=1;i<M;++i)
	{
		printf("%d ",A[i]);
		if(visit[i])
			printf("/ ");
	}
	printf("%d\n",A[M]);
	
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		mes(visit,0);
		scanf("%d %d",&M,&K);
		int Max=0;
		ll sum=0;
		for(int i=1;i<=M;++i)
		{
			 scanf("%d",&A[i]);
			 if(Max<A[i])
			 	Max=A[i];
			sum+=A[i];			
		}
		ll left=Max,right=sum;
		
	核心思路:二分,在一定范围内,小于某个数,就会分出K+1段,而>=这个数,就能分出K段。所以一直在这个数前后波动,直到下界为1700,上界为1701,这个时候mid=1700,能分出K段,<=K,上界也变成了1700,退出循环 
		while(right>left)
		{
			ll mid=(left+right)/2;
			if(divide(mid)<=K)
				right=mid;
			else
				left=mid+1;
		}	
		outPut(right);		
	}     
	return 0;
}

9、 Balanced Sequence HDU - 6299
题意:给出n个字符串,字符串的顺序可随意变换,问组成括号匹配的数量
思路:主要是排序,讨论排序的情况

using namespace std;
const int maxn = 1e5+10;
struct node
{
   int l,r,add;
   bool operator<(const node& s)const
   {
       if(l<=r&&s.l>s.r)
           return false;
       if(l>r&&s.l<=s.r)
           return true;
       if(l<=r&&s.l<=s.r)
           return l>s.l;
       return r<s.r;
   }
}a[maxn];
char s[maxn];

int main()
{
   int T;
   scanf("%d",&T);
   while(T--)
   {
       int n;
       scanf("%d",&n);
       for(int i = 1; i <= n; i++)
       {
           a[i].l = a[i].r = a[i].add = 0;
           scanf("%s",s);
           int len = strlen(s);
           for(int j = 0; j < len; j++)
           {
               if(s[j]=='(')
               {
                   a[i].l++;
               }
               else
               {
                   if(a[i].l>0)
                   {
                       a[i].l--;
                       a[i].add++;
                   }
                   else
                       a[i].r++;
               }
           }
       }
       sort(a+1,a+n+1);
       //now代表左括号的数量 
       int now = 0;
       int ans = 0;
       for(int i = 1; i <= n; i++)
       {
           if(a[i].r>now)
               a[i].r = now;
           ans += a[i].r+a[i].add;
           now -= a[i].r;
           now += a[i].l;
       }
       printf("%d\n",ans*2);
   }
   return 0;
}

10、Problem H. Monster Hunter HDU - 6326
题意:给定一棵 n 个点的树,除 1 外每个点有一只怪兽,打败它需要先消耗 ai点 HP,再恢复 bi点 HP。求从 1 号点出发按照最优策略打败所有怪兽一开始所需的最少 HP。

思路:以 1 为根将树转化成有根树,那么每只怪兽要在父亲怪兽被击败后才能被击败。假如没有父亲的限制,会产生一个最优的攻击顺序:

const int maxn=1e5+50;

class Node
{
	public:
		ll a,b;
		int id,num;
		bool operator<(const Node&x) const
		{
			return min(-a,-a+b-x.a)<min(-x.a,-x.a+x.b-a);
		}
};
Node node[maxn];
vector<int> v[maxn];
priority_queue<Node> q;
int pre[maxn],visit[maxn];

//通过邻接表构造一个pre[]数组,用来表示路径 
//从根到叶,虽然存储的是双向的连接,但是只要确定了根节点
//就可以从根到叶,不断遍历下去 
void dfs_pre(int u,int fa)
{
	pre[u]=fa;
	for(int i=0;i<v[u].size();++i)
		if(v[u][i]!=fa)
			dfs_pre(v[u][i],u);
}

int find(int u)
{
	if(!visit[pre[u]])
		return pre[u];
	return pre[u]=find(pre[u]);
}



int main()
{
	int T,N,a,b;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&N);
		for(int i=1;i<=N;++i)
		{
			v[i].clear();
			pre[i]=i;
		}
		memset(visit,0,sizeof(visit));
		
		node[1].a=node[1].b=0;
		node[1].id=1,node[1].num=0;
		for(int i=2;i<=N;++i)
		{
			scanf("%lld%lld",&node[i].a,&node[i].b);
			node[i].id=i;
			node[i].num=0;
		}
		
		for(int i=1;i<N;++i)
		{
			scanf("%d%d",&a,&b);
			v[a].push_back(b);
			v[b].push_back(a);
		}
		dfs_pre(1,1);
				
		while(!q.empty())
			q.pop();
			
		for(int i=2;i<=N;++i)
			q.push(node[i]);
		
		int cnt=0;
		
		while(!q.empty())
		{
			Node now=q.top();
			q.pop();
			
			int u=now.id;
			if(visit[u])
				continue;
			//插入了更新后的点,原来的点,就可以跳过了 
			if(now.num!=node[u].num)
				continue;
			visit[u]=1;
			
			int fa=find(u);
			
			if(node[u].a>node[fa].b)
			{
				node[fa].a+=(node[u].a-node[fa].b);
				node[fa].b=node[u].b;
			}
			else
			{
				node[fa].b=node[fa].b-node[u].a+node[u].b;
			}
			
			if(fa>1)
			{
				node[fa].num=++cnt;
				q.push(node[fa]);
			}			
		} 		
		printf("%lld\n",node[1].a);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值