贪心算法
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;
}