poj1456(贪心,并查集(不要掉进dp的陷阱))

题目:https://vjudge.net/problem/POJ-1456

这题不能用dp来做。。。因为底线在明天的物品也可以放在今天来卖,而且题目中的底线时间可以使断续的,所以浪费很多计算而且十分复杂。

 

正解是用贪心,用上并查集,就可以只要遍历一次即可得到答案。并查集也可以用数组遍历时间相同的统一修改日期(等于goods[i].d的所有商品的日期统一向前移动一天)也可以,但是如果并查集就可以修改次数大大减少(而且在递归的时候很多时候还能自动修改: 

所以选择并查集。。。

 

但是并查集在定义的时候也会有很多细节需要注意。因为是贪心,所以按照价格来排序(不管日期),所以可能两个2天的中间加一个1天,所以在遍历到第二个2天的时候,由于第一个2天的已经通过并查集改成Fa[2]=1了,而第一次遍历到1天的时候又做了Fa[1]=0修改,所以在Find()操作的时候会有自动修改Fa[2]=1-->Fa[2]=Fa[1]=0。所以陷进来了:

(1)

如果初始化memset(Fa,-1,sizeof(Fa)),那么在第二次扫描到2天的物品的时候Fa[2]=Fa[1]=0,而Fa[1]=Fa[0]=-1所以永远不能达到Fa[x]=x的条件,所以永远跳不出Find()递归,所以栈溢出导致段错误。

(2)

如果初始化为memset(Fa,0,sizeof(Fa)),而如果初始输入数据的日期是不连续的,比如4 50 3 10 1 20 3 30 1,则在扫到50 3的时候有Fa[3]=2,而扫到20的时候有Fa[3]=Fa[2]=0,而Fa[2]=Fa[0]=0此时跳出,导致20这个物品不能加到ans中(因为是Find()>0的条件),导致答案偏小。

(3)

poj1456(贪心,并查集(不要掉进dp的陷阱))_第1张图片

Find函数中的2种错误方式。

 

代码:

//poj1456
#include
#include
#include
using namespace std;
#define MAXN 10010

struct Node
{
    int p;
    int d;
}goods[MAXN];
int Fa[MAXN];

bool cmp(Node a,Node b)
{
    return a.p>b.p;
}

int Find(int x)
{
    /*if(Fa[x]!=-1)
        Fa[x]=Find(Fa[x]);
    return x;*/
    
    /*if(Fa[x]==-1)
        return x;
    Fa[x]=Find(Fa[x]);*/
    if(Fa[x]==-1)
        return x;
    return Fa[x]=Find(Fa[x]);
}

int main()
{
    int i;
    int N;
    int fa;
    int ans;

    while(scanf("%d",&N)!=EOF)
    {
        memset(Fa,-1,sizeof(Fa));
        memset(goods,0,sizeof(goods));
        for(i=1;i<=N;i++)
            scanf("%d%d",&goods[i].p,&goods[i].d);
        sort(goods+1,goods+1+N,cmp);
        ans=0;
        for(i=1;i<=N;i++)
        {
            fa=Find(goods[i].d);
            if(fa>0)
            {
                ans+=goods[i].p;
                Fa[fa]=fa-1;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

你可能感兴趣的:(poj1456(贪心,并查集(不要掉进dp的陷阱)))