【NOIP2016】愤怒的小鸟

本文详细介绍了CJOJ P2257【NOIP2016】愤怒的小鸟这道题目的解题思路,通过状态压缩动态规划的方法来解决如何使用最少数量的小鸟覆盖所有目标的问题。文章提供了完整的代码实现,并解释了关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CJOJ——P2257 【NOIP2016】愤怒的小鸟

题意

  给定n只猪的坐标,每一只小鸟能走出一条抛物线的轨迹,至少需要多少只鸟才能把这些猪全部打掉

解法

状压DP
  先对题目输入的坐标进行处理:
  枚举两只猪,求出抛物线,然后计算有多少只猪在这条抛物线上,利用状态压缩进行储存
  注意,对于单独的,不在任意一条上述抛物线上的猪要额外加一条抛物线
  对于从02n2开始的每一个状态now,都枚举所有的抛物线状态Pi,令u=now|Pi,则u为目标状态
  令fu表示要实现u需要多少条抛物线
  则fu=minfufnow+1f2n1就是答案

复杂度

O(n2+2ncnt),cnt为抛物线条数

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
bool vis[20];
double x[20];
double y[20];
int f[(1<<20)];//状压进行记忆化搜索,0表示不在当前状态的抛物线上,1则反之
int p[20*20];//表示用一只小鸟在打中编号为i和j的小猪的前提下所能打中的小猪序列,如001010100......
int T,n,m,ans,cnt;
int main( )
{
    double X,Y,a,b;
    scanf("%d",&T);
    while( T-- )
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<=n-1;i++)
        {
            scanf("%lf%lf",&X,&Y);
            x[ i ]=X;
            y[ i ]=Y;
        }
        memset( f,100,sizeof f );
        memset( vis,0,sizeof vis );
        memset( p,0,sizeof p );
        f[ 0 ]=0;
        cnt=0;
        for(int i=0;i<=n-1;i++)//枚举两个点,进行计算,求出抛物线
            for(int j=i+1;j<=n-1;j++)
            {
                if( x[ i ]==x[ j ] )
                    continue ;
                /*
                  解方程y=a*x*x+b*x:
                  易知:a=( y-b*x )/( x*x )
                  a*x[ i ]*x[ i ]+b*x[ i ]=y[ i ]  -----①
                  a*x[ j ]*x[ j ]+b*x[ j ]=y[ j ]  -----②
                  ①*x[ j ]*x[ j ]
                  ②*x[ i ]*x[ i ]
                  ①-②:
                  b*x[ i ]*x[ j ]*x[ j ]-b*x[ j ]*x[ i ]*x[ i ]=y[ i ]*x[ j ]*x[ j ]-y[ j ]*x[ i ]*x[ i ]
                  合并同类项:
                  b*x[ i ]*x[ j ]*(x[ j ]-x[ i ])=y[ i ]*x[ j ]*x[ j ]-y[ j ]*x[ i ]*x[ i ]
                  系数化为1:
                  b=( y[ i ]*x[ j ]*x[ j ]-y[ j ]*x[ i ]*x[ i ] )/( x[ i ]*x[ j ]*(x[ j ]-x[ i ]) )
                */
                //a=( y[ i ]*x[ j ]-y[ j ]*x[ i ] )/( x[ i ]*x[ i ]*x[ j ]-x[ j ]*x[ j ]*x[ i ] );
                b=( y[ i ]*x[ j ]*x[ j ]-y[ j ]*x[ i ]*x[ i ] )/( x[ i ]*x[ j ]*(x[ j ]-x[ i ]) );
                a=(y[ j ]-b*x[ j ])/(x[ j ]*x[ j ]);
                if( a>=-(1e-6) )//题目要求a<0
                    continue;
                cnt++;
                vis[ i ]=vis[ j ]=1;
                for(int k=0;k<=n-1;k++)//看还有多少只猪在这个抛物线上
                    if( abs( ( a*x[ k ]*x[ k ]+b*x[ k ] )-y[ k ] )<(1e-8) )//判断第k只猪是否在抛物线上
                    {
                        vis[ k ]=1;
                        p[ cnt ]|=(1<<k);
                    }
            }
        //处理只能过一点的抛物线
        for(int i=0;i<=n-1; i++)
            if( !vis[ i ] ) 
                p[ ++cnt ]|=(1<<i);
        //枚举状态与抛物线 
        int u;
        for(int now=0;now<=(1<<n)-2;now++)
            for(int i=1;i<=cnt;i++)
            {
                u=now|p[ i ];
                if( f[ u ]>f[ now ]+1 )
                    f[ u ]=f[ now ]+1;
            }
        printf("%d\n",f[ (1<<n)-1 ] );
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值