【洛谷2831】愤怒的小鸟(状压DP)
- 一个二维平面上有\(n\)个猪头。
- 每次你可以选择一条抛物线\(Ax^2+Bx(A<0)\),则所有落在这条抛物线上的猪头都会被杀死。
- 问至少需要几条抛物线才能杀死所有猪头。
- 数据组数\(\le5,n\le18\)
我居然现在才做掉这道题,实在太菜。。。
状压\(DP\)
一看数据范围显然状压,考虑用\(f_S\)表示当前已杀死猪头状压结果为\(S\)的最小步数。
众所周知三点确定一条抛物线,但由于此题\(C=0\),所以只需要两点就可以了。
于是就有一个\(O(n^3)\)的暴力转移:枚举两个尚未杀死的猪头确定一条抛物线,然后再枚一遍剩余猪头看看哪些猪头在这条抛物线上。
可手算一下复杂度有点虚,这时就需要一个显然且套路的优化:当前存活的编号最小的猪头终究是要被杀死的,我们不妨强制在这次杀死它,就可以少枚举一个在抛物线上的猪头。
于是转移就优化到了\(O(n^2)\)。
代码:\(O(n^22^n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 18
#define DB double
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define eps 1e-12
using namespace std;
int n,m,f[1<<N];struct P {DB x,y;}p[N+5];
int main()
{
RI Tt,i,j,k,x,t;DB A,B;scanf("%d",&Tt);W(Tt--)
{
for(scanf("%d%d",&n,&m),m=1<<n,i=1;i<=n;++i) scanf("%lf%lf",&p[i].x,&p[i].y);
for(i=0;i^m;++i) f[i]=n;for(f[0]=i=0;i<m-1;++i) if(f[i]^n)//初始化状态
{
for(x=1;(i>>x-1)&1;++x);for(Gmin(f[i|(1<<x-1)],f[i]+1),j=x+1;j<=n;++j)//强制杀死编号最小的猪头,枚举另一猪头
{
if((i>>j-1)&1^1&&fabs(p[x].x-p[j].x)<eps) continue;t=i|(1<<x-1)|(1<<j-1);//如果横坐标相等显然无解
A=(p[j].y/p[j].x-p[x].y/p[x].x)/(p[j].x-p[x].x),B=(p[x].y-A*p[x].x*p[x].x)/p[x].x;//解出解析式Ax^2+Bx
if(A>-eps) continue;for(k=j+1;k<=n;++k)//如果A≥0无解,否则枚举剩余猪头
fabs(A*p[k].x*p[k].x+B*p[k].x-p[k].y)<eps&&(t|=1<<k-1);Gmin(f[t],f[i]+1);//在抛物线上则压到新状态里,最后转移
}
}printf("%d\n",f[m-1]);//输出所有猪头都被杀死的最小步数
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒