NOIP2016Day2T3愤怒的小鸟(状压dp) O(2^n*n^2)再优化

  看这范围都知道是状压吧。。。

  题目大意就不说了嘿嘿嘿

  网上流传的写法复杂度大都是O(2^n*n^2),这个复杂度虽然官方数据可以过,但是在洛谷上会TLE【百度搜出来前几个博客的代码交上去都TLE了】,于是造成了洛谷上这题提交5k3只AC了250人,AC率只有4.7%。。。【这么卡常真的丧病,还是因为老爷机?

  所以我就写写怎么优化吧OWO

  首先还是先求出两两猪的解析式和能穿过多少只猪,记得如果两个猪的横坐标相同就要continue,a和b的公式随手推一推。然后枚举状态数,再枚举哪两只猪被射,记得处理只射一只猪的情况。

  f[i|num[j][k]]=min(f[i|num[j][k]],f[i]+1);【num[j][k]为一只穿过第j只猪和第k只猪的鸟发射后的状态

  这就是(2^n*n^2)的写法,接下来优化。

  对于我们枚举的每一个状态i,我们找到它正数第一只没射掉的猪进行转移后break。

  因为如果我们转移了第一只后面的没射的猪,到时候还要回头来将第一只猪射掉,所以后面的没射的猪的转移其实是多余的,射完第一只猪后接着往后射就可以了。

  【当然只用正数第二只猪或者第三只猪或第四只或第五只转移都是可以的,一个道理。。。显然第一只最好写吧233

  优化之后就可以过洛谷的这题辣。

代码如下:

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
int n,m,t,num[20][20],f[1048576];
double x[20],y[20];
bool same(double x,double y){return fabs(x-y)<1e-6;}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        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++)num[i][j]=0;
        for(int i=1;i<n;i++)
        for(int j=i+1;j<=n;j++)
        {
            if(same(x[i],x[j]))continue;
            double a=(y[j]/x[j]-y[i]/x[i])/(x[j]-x[i]);
            if(a>=0)continue;
            double b=y[i]/x[i]-a*x[i];
            for(int k=1;k<=n;k++)
            if(same(a*x[k]+b,y[k]/x[k]))num[i][j]|=(1<<(k-1));
        }
        for(int i=0;i<=(1<<n)-1;i++)f[i]=233333333;f[0]=0;
        for(int i=0;i<=(1<<n)-1;i++)
        for(int j=1;j<=n;j++)
        if(!(i&(1<<(j-1))))
        {
            for(int k=j;k<=n;k++)
            {
                if(j==k)f[i|(1<<(j-1))]=min(f[i|(1<<(j-1))],f[i]+1);
                if(same(x[j],x[k]))continue;
                f[i|num[j][k]]=min(f[i|num[j][k]],f[i]+1);
               }
               break;
       }
        printf("%d\n",f[(1<<n)-1]);
    }
}
View Code

转载于:https://www.cnblogs.com/Sakits/p/6440722.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值