POJ 1456 Supermarket(贪心算法,可用并查集优化)
http://poj.org/problem?id=1456
题意:
有n件商品需要卖,每件商品由(p,t)描述。其中p表示该商品被卖出可获得的利润,t表示该商品被卖出的截止时间。时间从1开始计时,每件商品被卖出的话需要占用1个时间单位。如果某件商品的t=3,那么该商品最多只能在时间1,时间2或时间3 这3个时间点上卖。
现在的问题是:对于给定的所有商品,我们如何安排每个时间点卖的商品能获得最大利润。输出最大利润即可。
分析:
本题是一道贪心算法,就是要求一个安排方式,即每个时间点到底放哪个商品,下面将分4步来解决本题。
一:本题的贪心解法。
将所有商品按利润从大到小排序,优先取当前利润最大的商品,将它放在自己的截止时间点t上。如果该时间点已经被放了商品(这个被放的商品肯定具有更大的利润,所以被优先安排了),那么就继续看t-1时刻是否可用,t-2时刻是否可用,直到1时刻。
二:贪心算法证明。
基本的贪心问题都是要你在一个解集中找出那个最优化的解。比如有一个解集包括了所有安排商品的可能组合:{1234,4321,1243,4231},那么最优的解假设为1234,我们的贪心算法其实就是按贪心步骤将原始解集(包含所有合法的解)一步步的缩小直到其中只有1个解的时候,那个解必然就是最优解。
证明第一步:算法第一步不会丢失最优解。
假设我们放下了最大利润的商品在它的截止时间点上,那么解集就从所有可行解 缩小到了 那些(最大利润商品在它的截止时间点上的)任意可行解 ,我们能保证这个缩小了的解集中一定包含。假设存在一个最优解它里面没有安排最大利润商品去卖,且这个最大利润的商品在tx时刻截止。所以我们可以将那个最优解安排在tx时刻的商品替换成最大利润的商品,最终我们可得一个更优解。倒出矛盾,所以一定存在至少一个最优解包含了最大利润的商品在它的截止时间点上。
证明第二步:算法第二步依然不会丢失最优解。
在放下最大利润商品后得到了一个缩小的解集,此时我们来放 次大利润的商品。
如果次大利润的商品不能放在它的截止时间点上,那么一定是最大利润的商品占了位置且没有其他可行位置了,此时肯定不放次大利润商品。这个结果会让解集继续缩小(排除所有包含次大利润物品的解),但依然不会丢失最优解(因为最大利润和次大利润商品本来就不能共存)。
如果次大利润的商品(按贪心算法步骤)能放下(在某个时间点),那么将次大利润的商品放在那个时间点会使得第一步算法得到的解集继续缩小,但是依然不会丢失最优解。即该再次被缩小的解集一定包含某个最优解,该最优解中在最大利润物品的截止时间上放了最大利润的商品,在次大利润物品的截止时间(或截止时间的前一个时刻)上放了次大利润的商品。
依次类推,贪心第3步,第4步,第n步依然不会丢失最优解,所以当解集只包含一个解的时候(或贪心算法考虑完每一个商品的时候),那个剩余的解一定就是一个最优解。
三. 举例分析。
假设有重量为1,2,3,4,5,6,7,8,9的9个商品,我们希望取3个商品出来,使得3个商品的重量和最大。
贪心算法:将所有商品从大到小取,取到3个商品时停止。即优先取重量大的商品。
贪心解集分析:
原始可行解集为:{123,321,124,421,125,521,...189,981...789,987} (即任意3个不同的10以内整数组合都是一个可行解)
当我们做了贪心第一步的时候(即选完了最大重量的物品9时),
原始解集缩小为解集1: {912, 913, 914,...} (即任何以9开头的可行解都是解集1的元素,其中最优解987,978,肯定也在里面,所以第一步没有丢失最优解)
当我们做了贪心第二步的时候(即选完了次大重量的物品8时),
解集1缩小为解集2:{987, 986, 985,984,983,982,981} (当前解集依然存在一个最优解987,所以贪心第2步依然没有丢失最优解)
当我们做了贪心第三步的时候(即选完了第三大重量的物品7时),
解集2缩小为解集3:{987} (此时解集就只剩1个最优解了)
总结:通过上述举例可知,贪心算法的执行过程就是一步步缩小解集的过程。只要保证每步贪心步骤不会让解集丢失最优解,那么贪心算法就是正确的。
四:利用并查集优化。
当我们处理第i个商品的时候,我们正常来说应该是从i的截止时间往前找,看看有没有空闲时间点来放商品i。这样查找的过程比较慢。
这里我们用并查集来做些优化,即对于时间点t来说,如果t时间点空闲,那么fa[t]==-1。 我们用findset(t)查找可行时间点时直接就可以返回t(因为t本来就可行)。如果t时间点不空闲,那么我们令fa[t]==t-1。我们用findset(t)查找可行时间点时返回的(不一定是t-1,如果t-1被占用,返回的就不是t-1。否则返回t-1)是从t-1时间点开始递归查找的结果。
AC代码(新):63ms
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=10000+5;
struct Node//商品
{
int p;//利润
int t;//截止时间
bool operator<(const Node &rhs)const
{
return p>rhs.p;
}
}nodes[maxn];
//findset(t)表示:t时间前的最晚一个可用时间点(可以是t)
int fa[maxn];
int findset(int x)
{
return fa[x]==-1? x:fa[x]=findset(fa[x]);
}
int main()
{
int n;
while(scanf("%d",&n)==1)
{
for(int i=0;i<n;i++)
{
scanf("%d%d",&nodes[i].p,&nodes[i].t);
}
sort(nodes,nodes+n);
memset(fa,-1,sizeof(fa));
int cnt=0;//最终利润
for(int i=0;i<n;i++)
{
//找到i商品截止时间前的最晚一个可用时间点
int time=findset( (nodes[i].t) );
if(time>0)//时间可行
{
cnt += nodes[i].p;
fa[time]=time-1;
}
}
printf("%d\n",cnt);
}
return 0;
}
AC代码:141ms,未用并查集优化
//未用并查集优化
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=10000+100;
struct product
{
int v,t;
bool operator < (const product &b)const
{
return v>b.v;//价值越大排名越前
}
}pro[MAXN];
int time[MAXN];//time[i]=x表示在时间点i上放了价值x的商品准备卖
int main()
{
int n;
while(scanf("%d",&n)==1)
{
int ans=0;//最大利益
memset(time,0,sizeof(time));
int max_time=0;//用来记录所有商品中的最大截止时间
for(int i=0;i<n;i++)
{
scanf("%d%d",&pro[i].v,&pro[i].t);
max_time = max(max_time , pro[i].t);
}
sort(pro,pro+n);
for(int i=0;i<n;i++)
{
for(int j=pro[i].t;j>=1;j--)//尝试安排第i大价值商品的出售时间
{
if(time[j]==0)
{
time[j]=pro[i].v;
break;
}
}
}
for(int i=1;i<=max_time;i++)
ans += time[i];
printf("%d\n",ans);
}
return 0;
}
AC代码: 并查集优化79ms
//并查集优化
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=10000+100;
struct product
{
int v,t;
bool operator < (const product &b)const
{
return v>b.v;//价值越大排名越前
}
}pro[MAXN];
int time[MAXN];//time[i]=x表示在时间点i上放了价值x的商品准备卖
int F[MAXN];
int findset(int i)
{
if(F[i]==-1)return i;
return F[i]=findset(F[i]);
}
int main()
{
int n;
while(scanf("%d",&n)==1)
{
int ans=0;//最大利益
memset(time,0,sizeof(time));
memset(F,-1,sizeof(F));
for(int i=0;i<n;i++)
{
scanf("%d%d",&pro[i].v,&pro[i].t);
}
sort(pro,pro+n);
for(int i=0;i<n;i++)
{
int t = findset(pro[i].t);//找到pro[i].t这个时间往前的第一个空闲时间点
if(t>0)
{
F[t]=t-1;
ans += pro[i].v;
}
}
printf("%d\n",ans);
}
return 0;
}