DP - 状压DP - NOIP2016 - 愤怒的小鸟
题意:
T 组 测 试 用 例 , 每 组 包 括 n 个 点 的 坐 标 。 T组测试用例,每组包括n个点的坐标。 T组测试用例,每组包括n个点的坐标。
现 需 要 用 过 原 点 的 抛 物 线 y = a x 2 + b x ( a < 0 ) , 来 覆 盖 这 n 个 点 , 问 至 少 需 要 多 少 条 抛 物 线 。 现需要用过原点的抛物线y=ax^2+bx(a<0),来覆盖这n个点,问至少需要多少条抛物线。 现需要用过原点的抛物线y=ax2+bx(a<0),来覆盖这n个点,问至少需要多少条抛物线。
输入样例:
2
2 0
1.00 3.00
3.00 3.00
5 2
1.00 5.00
2.00 8.00
3.00 9.00
4.00 8.00
5.00 5.00
输出样例:
1
1
数据范围:
1 < = n < = 18 , 0 < x i , y i < 10 。 时 / 空 限 制 : 2 s / 128 M B 1<=n<=18,0<x_i,y_i<10。\\时/空限制:2s / 128MB 1<=n<=18,0<xi,yi<10。时/空限制:2s/128MB
分析:
对 于 方 程 y = a x 2 + b x , 任 意 两 点 可 以 确 定 参 数 a 和 b , n 个 点 最 多 有 n 2 条 抛 物 线 。 对于方程y=ax^2+bx,任意两点可以确定参数a和b,n个点最多有n^2条抛物线。 对于方程y=ax2+bx,任意两点可以确定参数a和b,n个点最多有n2条抛物线。
问 题 转 化 为 从 n 2 条 抛 物 线 中 最 少 选 择 几 条 , 能 够 覆 盖 n 个 点 , 这 n 2 条 抛 物 线 可 以 先 暴 力 预 处 理 出 来 。 问题转化为从n^2条抛物线中最少选择几条,能够覆盖n个点,这n^2条抛物线可以先暴力预处理出来。 问题转化为从n2条抛物线中最少选择几条,能够覆盖n个点,这n2条抛物线可以先暴力预处理出来。
接 着 可 以 用 一 个 n 位 二 进 制 数 表 示 哪 些 点 已 经 被 覆 盖 了 , 对 于 没 有 被 覆 盖 的 点 x , 我 们 再 枚 举 所 有 能 够 覆 盖 x 的 抛 物 线 , 若 所 有 点 均 被 覆 盖 了 ( n 位 全 1 ) , 就 更 新 一 下 使 用 的 抛 物 线 条 数 。 接着可以用一个n位二进制数表示哪些点已经被覆盖了,对于没有被覆盖的点x,我们再枚举所有能够覆盖x的抛物线,\\若所有点均被覆盖了(n位全1),就更新一下使用的抛物线条数。 接着可以用一个n位二进制数表示哪些点已经被覆盖了,对于没有被覆盖的点x,我们再枚举所有能够覆盖x的抛物线,若所有点均被覆盖了(n位全1),就更新一下使用的抛物线条数。
解 参 数 a , b : 对 于 ( x 1 , y 1 ) 和 ( x 2 , y 2 ) 所 确 定 的 抛 物 线 , 有 方 程 组 { y 1 = a x 1 2 + b x 1 y 2 = a x 2 2 + b x 2 , 即 { y 1 x 1 = a x 1 + b y 2 x 2 = a x 2 + b , 两 式 作 差 , 得 { a = y 1 x 1 − y 2 x 2 x 1 − x 2 b = y 1 x 1 − a x 1 , ( x 1 ≠ x 2 且 x 1 ≠ 0 ) 。 解参数a,b:\\对于(x_1,y_1)和(x_2,y_2)所确定的抛物线,有方程组\begin{cases}y_1=ax_1^2+bx_1\\y_2=ax_2^2+bx_2\end{cases},即\begin{cases}\frac{y_1}{x_1}=ax_1+b\\\frac{y_2}{x_2}=ax_2+b\end{cases},\\两式作差,得\begin{cases}a=\frac{ \frac{y_1}{x_1}-\frac{y_2}{x_2} }{x_1-x_2} \\ \ \\b=\frac{y_1}{x_1}-ax_1\end{cases},(x_1≠x_2且x_1≠0)。 解参数a,b:对于(x1,y1)和(x2,y2)所确定的抛物线,有方程组{y1=ax12+bx1y2=ax22+bx2,即{x1y1=ax1+bx2y2=ax2+b,两式作差,得⎩⎪⎨⎪⎧a=x1−x2x1y1−x2y2 b=x1y1−ax1,(x1=x2且x1=0)。
具体落实:
f [ s t a t e ] : 表 示 覆 盖 状 态 为 s t a t e 需 要 的 抛 物 线 条 数 。 p a t h [ i ] [ j ] : 表 示 点 i 和 点 j 所 确 定 的 抛 物 线 能 够 覆 盖 的 二 进 制 状 态 。 f[state]:表示覆盖状态为state需要的抛物线条数。\\path[i][j]:表示点i和点j所确定的抛物线能够覆盖的二进制状态。 f[state]:表示覆盖状态为state需要的抛物线条数。path[i][j]:表示点i和点j所确定的抛物线能够覆盖的二进制状态。
① 、 预 处 理 p a t h 数 组 , 尤 其 注 意 两 个 点 的 横 坐 标 不 能 相 同 , 抛 物 线 开 口 要 朝 下 ( a < 0 ) 。 ①、预处理path数组,尤其注意两个点的横坐标不能相同,抛物线开口要朝下(a<0)。 ①、预处理path数组,尤其注意两个点的横坐标不能相同,抛物线开口要朝下(a<0)。
② 、 D P 求 f 数 组 , 对 于 每 一 个 状 态 i , 先 任 选 一 个 该 状 态 还 未 覆 盖 到 的 点 x , 接 着 枚 举 所 有 覆 盖 x 点 的 抛 物 线 所 能 覆 盖 的 状 态 p a t h [ x ] [ j ] , j ∈ [ 0 , n − 1 ] , i ∣ p a t h [ x ] [ j ] 即 表 示 加 上 这 条 新 的 抛 物 线 后 的 状 态 , f [ i ∣ p a t h [ x ] [ j ] ] = m i n ( f [ i ∣ p a t h [ x ] [ j ] ] , f [ i ] + 1 ) 。 即 更 新 新 状 态 的 最 小 值 。 ②、DP求f数组,对于每一个状态i,先任选一个该状态还未覆盖到的点x,\\\qquad接着枚举所有覆盖x点的抛物线所能覆盖的状态path[x][j],j∈[0,n-1],\\\qquad i\ |\ path[x][j]即表示加上这条新的抛物线后的状态,f[i\ |\ path[x][j]]=min(f[i\ |\ path[x][j]],f[i]+1)。\\\qquad即更新新状态的最小值。 ②、DP求f数组,对于每一个状态i,先任选一个该状态还未覆盖到的点x,接着枚举所有覆盖x点的抛物线所能覆盖的状态path[x][j],j∈[0,n−1],i ∣ path[x][j]即表示加上这条新的抛物线后的状态,f[i ∣ path[x][j]]=min(f[i ∣ path[x][j]],f[i]+1)。即更新新状态的最小值。
注意: 函 数 a b s ( i n t x ) , 是 求 整 数 绝 对 值 的 。 而 函 数 f a b s ( d o u b l e x ) , 是 求 小 数 绝 对 值 的 。 函数abs(int \ x),是求整数绝对值的。\\\ \ \qquad而函数fabs(double\ x),是求小数绝对值的。 函数abs(int x),是求整数绝对值的。 而函数fabs(double x),是求小数绝对值的。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#define P pair<double,double>
#define x first
#define y second
using namespace std;
const int N=20,M=1<<18;
double eps=1e-8;
int n,m,T;
int path[N][N];
int f[M];
P v[N];
int cmp(double a,double b)
{
if(fabs(a-b)<eps) return 0;
if(a<b) return -1;
return 1;
}
int main()
{
cin>>T;
while(T--)
{
cin>>n>>m;
for(int i=0;i<n;i++) cin>>v[i].x>>v[i].y;
memset(path,0,sizeof path);
for(int i=0;i<n;i++)
{
path[i][i]=1<<i;
for(int j=0;j<n;j++)
{
double x1=v[i].x,y1=v[i].y;
double x2=v[j].x,y2=v[j].y;
if(!cmp(x1,x2)) continue;
double a=(y1/x1-y2/x2)/(x1-x2);
double b=y1/x1-a*x1;
if(cmp(a,0)>=0) continue;
int State=0;
for(int k=0;k<n;k++)
{
double x=v[k].x,y=v[k].y;
if(!cmp(a*x*x+b*x,y))
State+=1<<k;
}
path[i][j]=State;
}
}
memset(f,0x3f,sizeof f);
f[0]=0;
for(int i=0;i+1<1<<n;i++)
{
int x=0;
for(int j=0;j<n;j++)
if(!(i>>j&1))
{
x=j;
break;
}
for(int j=0;j<n;j++)
f[i|path[x][j]]=min(f[i|path[x][j]],f[i]+1);
}
cout<<f[(1<<n)-1]<<endl;
}
return 0;
}