愤怒的小鸟 题解

这一题太坑了,卡高精卡得好死。
说正题,这一题除了卡高精就是状压了,当然也可以爆搜加剪枝在此我们就聊一下状压DP,当时看到这一题还无从入手想着只能暴力了,后面看到数据才18只猪^(* ̄(oo) ̄)^,不用说了,状压DP,我们可以用二进制来状压猪到底有没有被打掉,然后后面看了看也没什么状态可以定义了,于是我们确定状态dp[i]表示打掉i集合中的猪最少需要多少只鸟,下面我们讲一下集合,其实就是几只猪组合在一起就叫做集合,举个栗子:假设有五只猪,我们用二进制10011表示第一只猪、第四只猪、第五只猪所在的集合,通俗地讲就是一表示有对应位置编号的猪。定义玩状态我们可以写出状态转移方程f[i]=min(f[i],f[i^a]+1),这只是伪代码,为什么是伪代码呢?原因很简单,因为a是i的子集,至于什么是子集,再举个栗子:集合10011的猪的编号就是1、4、5,这三只猪,那么只要有一个集合有这三只猪的任意几只(并且只能在这三只猪中选,不可以有其他猪),那么这些集合就是10011的子集。回到问题,枚举子集是很简单直接枚举所有集合,然后再判断是不是子集就ojbk了,可是这样的时间复杂度是O(2^(2n))2的2n次方,肯定TLE,那么我们考虑一下题目看看可不可以优化,很直接不能直接枚举子集,那么我们可不可以枚举每只猪呢?答案是可以的这样时间复杂度就是O(2^(n+1))算了一下可以过,那么问题来了枚举每只猪该怎么转移呢?实际上这个题目并不在乎我们打的顺序,而且顺序也是没关系的,所以本着能打则打的原则加上从小的编号的猪开始打就可以了,这样状态转移方程就搞定了,下面讲一下预处理,我们用g[i][j]表示第i号猪和第j号猪所在的抛物线上所有猪组成的集合,这样就搞定了,具体实现看代码,有注释。
上代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define eps 1e-6//因为高精度在计算时会掉精度所以要有个范围这个范围是最保险的如果比它小的话就可能会错,这句话就是定义一个eps的变量,把它值赋为0.0000001 
using namespace std;
struct zhu
{
    double x,y;
}pig[20];//存我们可爱的猪^(* ̄(oo) ̄)^ 
int g[20][20],dp[1<<19];
int main()
{
    int t;
    scanf("%d",&t);
    while(t)
    {
        memset(pig,0,sizeof(pig));//清空一下,因为我们有很多关 
        memset(g,0,sizeof(g));
        int n,m,i,j,k;
        scanf("%d %d",&n,&m);//读入 
        for(int i=1;i<=n;i++)
        {
            scanf("%lf %lf",&pig[i].x,&pig[i].y);
        }
        for(i=1;i<=n;i++)//枚举每只猪 
        {
            for(j=1;j<=n;j++)//枚举每只猪和上一只组成一条抛物线 
            {
                if(i==j)
                    continue;
                double a,b,x1=pig[i].x,y1=pig[i].y,x2=pig[j].x,y2=pig[j].y;
                a=(x2*y1-x1*y2)/(x1*x2*(x1-x2));//计算一下,这个是数学知识,认真去写一下都可以写出来 
                b=(x1*x1*y2-x2*x2*y1)/(x1*x2*(x1-x2));
                if(a>-eps)//这个是高精,原本是a>0的可是因为精度问题所以这么写 
                    continue;
                for(int k=1;k<=n;k++)//枚举每只猪 
                    if(fabs(a*pig[k].x*pig[k].x+b*pig[k].x-pig[k].y)<=eps)//看看这猪有没有在抛物线上,fabs是精度类型的函数,可以取精度类型的数的绝对值,在函数库cmath中 
                        g[i][j]=g[i][j]|(1<<(k-1));//有的话在相应位置上标成1 
            }
        }
        dp[0]=0;//设置一下边界 
        for(i=1;i<(1<<n);i++)
        {
            for(j=1;j<=n;j++)//枚举在集合i中编号最小的猪 
                if(i&(1<<(j-1)))//为什么是只要一个就好呢?因为我们DP本来就是所有情况都计算了,然后无论枚举最大还是最小都可以达到全部枚举一遍的效果 
                    break;//注意只能是最大或者最小,因为如果你特定一个点的话,首先不可以确定在集合i中,再然后特定就不可以达到全部的效果,这个好好想一下,最好手动模拟一组数据,这个点是优化的重点 
            dp[i]=dp[i^(1<<(j-1))]+1;//i可以从i集合中除j这个点的集合转移过来 
            for(k=1;k<=n;k++)//枚举所有猪 
            {
                if(k!=j&&(1<<(k-1)&i))//如果这个猪在i中并且不是j号猪那么就可以从i集合中除了j,k这一条函数的猪之外的猪的集合转移过来 
                    dp[i]=min(dp[i],dp[i^(i&g[j][k])]+1);//选最小 
            } 
        }
        printf("%d\n",dp[(1<<n)-1]);
        t--;
    }
    return 0;
}

希望大家不要直接复制粘贴,可以自己打一遍,这样印象更深刻,才有用。希望大家都可以AC。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值