[P2831] 愤怒的小鸟

Link:

P2831 传送门

Solution:

其实是一道裸爆搜都能过的题目……

看到$nle 18$想到状压$dp$

令$dp[i]$,表示达到当前状态的最小代价

可以发现只要两个点就能确定一条从原点出发的抛物线

因此每次对于一个特定的状态枚举两点确定抛物线转移就好了

$O(n^3)$预处理出每两个点形成的抛物线能经过的状态$f[i][j]$,接下来$O(n^2*2^n)$转移

但每次一定要$O(n^2)$枚举所有的点对吗?

其实完全可以通过构造有序性来去掉一个$n$:

默认从小到大打猪,每次都打最小未被打的,毕竟先打了后面的也要再回头打前面的

这样就确定了点对中的第一个点:最小未被打的点

只要枚举另一个点就将复杂度降到了$O(n*2^n)$

Code:

#include <bits/stdc++.h>

using namespace std;
const int MAXN=1<<18,INF=1<<27;
const double eps=1e-6;
double x[20],y[20];
int T,n,m,dp[MAXN],f[20][20];

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
            scanf("%lf%lf",&x[i],&y[i]);
        memset(f,0,sizeof(f));
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
            {
                if(i==j){f[i][j]=(1<<i);continue;}
                double a=(y[j]/x[j]-y[i]/x[i])/(x[j]-x[i]);
                double b=y[i]/x[i]-a*x[i];
                if(a>-eps) continue;
                
                for(int k=0;k<n;k++)
                    if(fabs(a*x[k]*x[k]+b*x[k]-y[k])<=eps)
                        f[i][j]|=(1<<k);
            }
        
        for(int i=0;i<MAXN;i++) dp[i]=INF;
        int fst;dp[0]=0;
        for(int i=0;i<(1<<n);i++)
        {
            if(dp[i]==INF) continue;
            for(fst=0;fst<n;fst++)
                if(!(i&(1<<fst))) break;            
            for(int j=0;j<n;j++)
                dp[i|f[fst][j]]=min(dp[i|f[fst][j]],dp[i]+1);
        }
        printf("%d
",dp[(1<<n)-1]);
    }
    return 0;
}

Review:

学会利用构造有序性的方式减掉一维的枚举

原文地址:https://www.cnblogs.com/newera/p/9303735.html