[NOIP2016 提高组] 愤怒的小鸟

Posted Jozky86

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[NOIP2016 提高组] 愤怒的小鸟相关的知识,希望对你有一定的参考价值。

[NOIP2016 提高组] 愤怒的小鸟

题意:

有n只猪,给出猪的坐标(xi,yi),问最少用几个形如 y=ax^2+bx 的曲线可以保证所有猪在曲线上,满足a<0,a,b为实数
n<=18,

题解:

两个方法:爆搜或者状压dp

状压dp

看n<=18也就能看出来,这数据范围就是用状压dp
我们用二进制来表示哪些猪已经被曲线标记,比如11(十进制),二进制下是000001011,表示第1,2,4头猪已经被标记
首先对于每个状态,我们用state[i]记录其第一次0出现的位置(从右往左),这将作为我们dp的起点

for(int i=0;i<(1<<18);i++){
		int j=1;
		for(;j<=18 && i&(1<<(j-1));j++);
		start[i]=j;//记录该状态下,出现第一个0的位置 
	}

然后我们枚举两个点带入到式子y=ax^2+bx,求出a和b,a必须大于0,然后枚举所有点,看哪些点在这个抛物线上,用line[i][j]来记录能经过哪些猪🐖
然后对于每一种状态,起点就是我们之前求的0的第一次位置,然后枚举其他点x,x有可能在抛物线上,也有可能单独成抛物线,取最小
详细看代码

爆搜

参考题解
对于每个猪,如果他不能存在于之前的抛物线,就与其他猪构成抛物线,然后继续搜。还有一种选择:暂时不与其它猪组成抛物线
代码里有大量详细的注释

代码:

状压dp

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8;
int T,n,m,lines[19][19],start[1<<19],dp[1<<19];
double x[19],y[19];
void equation(double &a,double &b,int i,int j){
	a=-(y[i]*x[j]-y[j]*x[i])/(x[j]*x[j]*x[i]-x[i]*x[i]*x[j]);
	b=(y[i]*x[j]*x[j]-y[j]*x[i]*x[i])/(x[i]*x[j]*x[j]-x[j]*x[i]*x[i]);
}
int main(){
	for(int i=0;i<(1<<18);i++){
		int j=1;
		for(;j<=18 && i&(1<<(j-1));j++);
		start[i]=j;//记录该状态下,出现第一个0的位置 
	}
	scanf("%d",&T);
	for(int i=1;i<=T;i++)
	{
		memset(lines,0,sizeof(lines));
		memset(dp,1,sizeof(dp));
		dp[0]=0;
		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=1;j<=n;j++)
			{
				if(fabs(x[i]-x[j])<eps) continue;//横坐标相等 
				double a,b;
				equation(a,b,i,j);
				if(a>-eps) continue;//必须开口向下 
				
				for(int k=1;k<=n;k++)//枚举被抛物线经过的点 
					if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<eps)
						lines[i][j]|=(1<<(k-1));

			}
			
		for(int i=0;i<(1<<n);i++)
		{
			int j=start[i];
			
			dp[i|(1<<(j-1))]=min(dp[i|(1<<(j-1))],dp[i]+1); 
			
			for(int k=1;k<=n;k++)
				dp[i|lines[j][k]]=min(dp[i|lines[j][k]],dp[i]+1); 
		}
		printf("%d\\n",dp[(1<<n)-1]);
	}
	return 0;
}

爆搜代码:

/*思路:搜索。
对于第i只猪,如果它已经被前面所构造的某条抛物线经过了,
就不用处理了,继续往下搜索。否则,就有两种选择,第一种
是与前面某一只单独的猪(即这只猪未与其它猪组成抛物线)
组成抛物线,因为两只猪最多也只能被一条抛物线相连;第二
种是暂时不与其它单独的猪组成抛物线。最后,将抛物线的条
数与余下的猪的个数相加(因为单独的一只猪也要一条抛物线
将其击中)就是搜索出来的一个合法的结果。*/ 
#include<iostream>
#include<cstdio>
#include<fstream>
#include<algorithm>
#include<string>
#include<sstream>
#include<cstring>
#include<cmath>
    using namespace std;
    const double eps=1e-8;
/*因为浮点数的精度计算太过复杂,像3.14这样的数存在浮
点型变量里存的可能是3.139999999,也有可能是3.140000001,
所以不能直接用“==”判断两个浮点数是否相等。在这种情况
下,就允许判断两个浮点数为相等时,两数之间存在微小的误差,
这个“微小的误差”要取一个较小的数,比如1e-8,这样就不
会判不出也不会误判了。*/
bool dy(double a,double b)//判断两个浮点数是否相等的函数 
{
    //如果a和b之间相差的值小于eps(即1e-8),就说明它们是相等的 
    return fabs(a-b)<eps;//fabs用于取浮点数的绝对值 
}
    int n=0,m=0,ans=0;
    //x数组存每只猪的x坐标,y数组存每只猪的y坐标
    //pwxa数组存每条已构造的抛物线的参数a,pwxb数组存每条已构造的抛物线的参数b
    //tx数组存每只单独的猪的x坐标,ty数组存每只单独的猪的y坐标 
    double x[20],y[20],pwxa[20],pwxb[20],tx[20],ty[20];
//dfs(c,u,v)表示当前搜到第c只猪,已构造抛物线的数量为u,单独的猪的数量为v时的情况 
//注意:当前抛物线的总数量为(u+v)条,因为除已有的抛物线外,每一只单独的猪也需要一条抛物线来击中它 
void dfs(int c,int u,int v)
{
    //最优性剪枝,因为即使后面的每一只猪都被当前已构造的抛物线击中, 
    //或者与其它单独的猪组成抛物线,抛物线的总数量还是(u+v),不会减少,
    //所以如果抛物线的总数量已经大于等于当前的最优解时,搜下去也不会
    //比当前更优了,就不用继续搜下去了。
    if(u+v>=ans) return;
    if(c>n)//如果搜完了 
    {
        ans=u+v;//记录答案,因为前面已经有最优性剪枝了,所以没被剪掉的答案一定比当前更优 
        return;//结束 
    }
    bool flag=false;//记录是否被已构造的抛物线经过 
    for(int i=1;i<=u;i++)//枚举已构造的抛物线 
        //将这一条抛物线的参数与当前猪的x坐标带进函数解析式算一遍,
        //如果结果等于当前猪的y坐标,就说明当前猪已经被已构造的抛物线经过了 
        if(dy(pwxa[i]*x[c]*x[c]+pwxb[i]*x[c],y[c]))
        {
            dfs(c+1,u,v);//被经过了就直接往下搜 
            flag=true;//记录 
            break;//退出循环,既然当前猪已经被经过了,再继续判断也没有意义了
        }
    if(!flag)//如果没有被经过 
    {
        for(int i=1;i<=v;i++)//枚举单独的猪 
        {
            //如果当前猪与这一只单独的猪的x坐标相同,就说明它们不能组成一条
            //抛物线。因为若想使它们组成一条抛物线,弹弓的位置就必须是它们的正下
            //方,然后往上垂直发射,但是弹弓的位置固定在(0,0),而猪的x坐标又大于
            //0(题目数据范围),所以它们不能组成一条抛物线。如果不加这个判
            //断的话就会计算出一些奇奇怪怪的东西,影响后面的判断,所以还是加上为好。 
            if(dy(x[c],tx[i])) continue;//如果当前猪与这一只单独的猪的x坐标相同,跳过这一次循环
            //求参数a、b的过程实际上就是解一个二元一次方程组,比如说有两只猪的坐标分别是(1,3)和(3,3), 
            //现在要求出它们所组成的抛物线的参数a与参数b。将两只猪的坐标分别代入到函数解析式中,
            //就可以得出一个方程组: 3=a+b  3=9a+3b 。这是,我们可以用加减消元法消掉b,将两个方程
            //分别乘上另一个方程的b的系数,得:9=3a+3b 3=9a+3b。这样,两个方程中的b的系数就一致了,
            //然后直接用一个式子减去另一个式子,再将a的系数化为一即可。最后再将a带入到回任意一个方程求b即可。
            //因为这里的所有方程都长一个样,所以这一种方法是通用的。 
            double a=(y[c]*tx[i]-ty[i]*x[c])/(x[c]*x[c]*tx[i]-tx[i]*tx[i]*x[c]);//求参数a 
            double b=(y[c]-x[c]*x[c]*a)/x[c];//将a代入求参数b 
            if(a<0)//如果这一个解是合法的,就说明当前猪与这一只单独的猪可以组成抛物线 
            {   
                pwxa[u+1]=a;//记录抛物线的参数a 
                pwxb[u+1]=b;//记录抛物线的参数b
                //将这一只单独的猪从数组中删去,因为它已经和当前猪组成抛物线,不再单独了 
                double q=tx[i],w=ty[i];
                for(int j=i;j<v;j++)
                {
                    tx[j]=tx[j+1];
                    ty[j]=ty[j+1];
                }
                dfs(c+1,u+1,v-1);//继续往下搜
                //将这一只单独的猪放回数组(回溯) 
                for(int j=v;j>i;j--) 
                {
                    tx[j]=tx[j-1];
                    ty[j]=ty[j-1];
                }
                tx[i]=q;
                ty[i]=w;
            }
        }
        //还有一种选择:暂时不与其它猪组成抛物线 
        tx[v+1]=x[c];
        ty[v+1]=y[c];
        dfs(c+1,u,v+1);//继续往下搜 
    }
}
int main()
{
    int T=0;
    scanf("%d",&T);
    for(int Q=1;Q<=T;Q++)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) 
			scanf("%lf%lf",&x[i],&y[i]);
        ans=100;//记得初始化 
        dfs(1,0,0);//搜 
        printf("%d\\n",ans); 
    } 
    return 0;//完美地结束 
}
//借鉴大神之作
//完美爆搜

以上是关于[NOIP2016 提高组] 愤怒的小鸟的主要内容,如果未能解决你的问题,请参考以下文章

[NOIp2016提高组]愤怒的小鸟

NOIP2016提高组愤怒的小鸟(状压宽搜)

NOIP提高组2016 愤怒的小鸟

[NOIP2016提高组]愤怒的小鸟

NOIP2016提高组day2愤怒的小鸟

愤怒的小鸟 NOIP2016 提高组 状压dp