2018.11.01【NOIP2016】【洛谷P2831】愤怒的小鸟(状压DP)

传送门


解析:

数据范围 18 18 18,多么显然的状压。。。

但是!!!为什么 O ( n 2 2 n ) O(n^22^n) O(n22n)能过???复杂度明显不对啊。。。

这里提供一种 O ( n 2 n ) O(n2^n) O(n2n)的做法(博主也是看了别人的题解学习的这种做法)

思路:

首先枚举所有合法的线(即枚举两只猪),预处理这条线能够穿过的猪。

然后直接大力 D P DP DP就是 O ( n 2 2 n ) O(n^22^n) O(n22n)
但是可以优化到 O ( n 2 n ) O(n2^n) O(n2n)

关于做法这里不作赘述,只谈优化。

我们为什么要枚举 O ( n 2 ) O(n^2) O(n2)条线?
我们只需要枚举 O ( n ) O(n) O(n)条线!

考虑集合当中编号最小的没有被考虑的猪 i i i,这可以预处理,可以用位运算(还是需要预处理,不过要简单的多)。这只猪在这次投射中必须被打中,不然我们在后面的状态中还要回过头来打它,这是多余的转移。

所以我们只枚举和这只猪有关的线就行了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline double getdb(){
    re double x,y=1.0;
    re char c;
    while(!isdigit(c=gc()));x=c^48;
    while(isdigit(c=gc()))x=(x*10)+(c^48);
    if(c!='.')return x;
    while(isdigit(c=gc()))x+=(y/=10)*(c^48);
    return x;
}

cs double eps=1e-6;
inline int sign(cs double &x){
    return fabs(x)<eps?0:(x>0?1:-1);
}

int lownbit[(1<<18)+3];
double x[20],y[20];
int line[20][20];
int dp[(1<<18)+3];

int T,n;
signed main(){
    for(int re i=0;i<18;++i)lownbit[1<<i]=i;
    scanf("%d",&T);
    while(T--){
        scanf("%d%*d",&n);
        for(int re i=0;i<n;++i)x[i]=getdb(),y[i]=getdb();
        memset(dp,0x3f,sizeof dp);
        memset(line,0,sizeof line);
        for(int re i=0;i<n;++i){
            for(int re j=0;j<n;++j){
                if(!sign(x[i]-x[j]))continue;
                double y_1=y[i]/x[i],x_1=x[i];
                double y_2=y[j]/x[j],x_2=x[j];
                double a=(y_1-y_2)/(x_1-x_2),b=y_1-x_1*a;
                if(a>-eps||b<0)continue;
                for(int re k=0;k<n;++k)if(!sign(a*x[k]+b-y[k]/x[k]))line[i][j]|=1<<k;
            }
        }
        dp[0]=0;
        for(int re i=0;i+1<(1<<n);++i){
            int j=lownbit[(~i)&(-(~i))];
            dp[i|(1<<j)]=min(dp[i|(1<<j)],dp[i]+1);
            for(int re k=0;k<n;++k)dp[i|line[j][k]]=min(dp[i|line[j][k]],dp[i]+1);
        }
        printf("%d\n",dp[(1<<n)-1]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值