状压dp NOIP 2016 愤怒的小鸟

题意:有 n n n只猪,第 i i i只坐标为 ( x i , y i ) (x_i,y_i) (xi,yi),问最少要用多少形如 y = a x 2 + b x y=ax^2+bx y=ax2+bx的抛物线才能将所有猪打下来,要求这些抛物线都过 ( 0 , 0 ) (0,0) (0,0)点,且 a &lt; 0 a&lt;0 a<0 ( n &lt; = 18 ) (n&lt;=18) (n<=18)

n的范围如此小,我们考虑状压dp,对于每一对猪 i , j i,j i,j,我们可以用三个点 ( 0 , 0 ) , ( x i , y i ) , ( x j , y j ) (0,0),(x_i,y_i),(x_j,y_j) (0,0),(xi,yi),(xj,yj)确定一条抛物线,如果该抛物线的 a &lt; 0 a&lt;0 a<0,就把它留下来,并求出这条抛物线可以打下那些猪。我们将每只猪是否被打下来的 01 01 01状态压缩成十进制数, d p [ i ] dp[i] dp[i]表示在状态 i i i下最少需要多少条抛物线。我们枚举所有状态,对于每一种状态,我们枚举所有合法的抛物线,转移方程为: d p [ i ∣ s [ j ] ] = m i n ( d p [ i ∣ s [ j ] ] , d p [ i ] + 1 ) ; , s [ j ] 为 第 j 条 抛 物 线 能 打 下 的 猪 。 dp[i|s[j]]=min(dp[i|s[j]],dp[i]+1);,s[j]为第j条抛物线能打下的猪。 dp[is[j]]=min(dp[is[j]],dp[i]+1);s[j]j线
但是可能有些猪不能被已经求出的所有抛物线打下来,因为可能与这些猪形成的所有的抛物线 a a a都大于 0 0 0,所以我们还要枚举每一头猪,看一头一头的打是否能更新答案。转移方程为: d p [ i ∣ ( 1 &lt; &lt; ( j − 1 ) ) ] = m i n ( d p [ i ∣ ( 1 &lt; &lt; ( j − 1 ) ) ] , d p [ i ] + 1 ) ; dp[i|(1&lt;&lt;(j-1))]=min(dp[i|(1&lt;&lt;(j-1))],dp[i]+1); dp[i(1<<(j1))]=min(dp[i(1<<(j1))],dp[i]+1);

要注意的地方:在判断一条抛物线能否打下某一头猪时,因为都是double型变量,所以要

if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<=eps)

以下做法可能会出锅QAQ

if(a*x[k]*x[k]+b*x[k]-y[k]==0)

最后的结果就是 d p [ ( 1 &lt; &lt; n ) − 1 ] dp[(1&lt;&lt;n)-1] dp[(1<<n)1]了。

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-6;
int T,n,m,dp[550000],s[550000],num;
double x[1000],y[1000];
int main()
{
    cin>>T;
    while(T--)
    {
        num=0;
        memset(x,0,sizeof(x));
        memset(y,0,sizeof(y));
        memset(s,0,sizeof(s));
        memset(dp,0x3f,sizeof(dp));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i)
            scanf("%lf%lf",&x[i],&y[i]);
        for(int i=1;i<=n;++i)
            for(int j=i+1;j<=n;++j)
            {
                if(x[i]!=x[j])
                {
                    double a=((x[i]*y[j])-(x[j]*y[i]))/((x[i]*x[j]*x[j])-(x[i]*x[i]*x[j]));
                    double b=(y[i]/x[i])-(((x[i]*y[j])-(x[j]*y[i]))/((x[j]*x[j])-(x[i]*x[j])));
                    if(a<0)
                    {
                        num++;
                        for(int k=1;k<=n;++k)
                            if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<=eps)
                                s[num]|=(1<<(k-1));
                    }	
                }
            }
        dp[0]=0;
        for(int i=0;i<(1<<n);++i)
        {
            for(int j=1;j<=num;++j)
                dp[i|s[j]]=min(dp[i|s[j]],dp[i]+1);//按抛物线打 
            for(int j=1;j<=n;++j)
                dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+1);//只打一头 
        }
        printf("%d\n",dp[(1<<n)-1]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值