题解:[NOIP 2016 提高组] 愤怒的小鸟
题意分析
注意到 \(n\leq18\),因此可以考虑状压 DP。
设 \(\textit{dp}_s\) 表示射死的猪的集合为 \(s\) 时的方案数。(\(s\) 放到状压的代码里面就是一个整数,二进制的第 \(i\) 位表示猪 \(i\) 是否被射死。)
那么考虑抛物线如何射死猪。
对于一条抛物线,如果射死一只猪,则有无穷多条;如果射死两只猪,则可以确定(因为抛物线形如 \(y=ax^2+bx\),必须过点 \((0,0)\))。
那么记 \(\textit{line}_{i,j}\) 表示射死第 \(i,j\) 只猪的合法的抛物线射死的猪的状态(猪从 \(0\) 至 \(n-1\) 编号)。
记 \((x_i,y_i),(x_j,y_j)\) 分别为猪 \(i,j\) 的坐标。
设 \(\textit{line}_{i,j}\) 对应抛物线为 \(y=ax^2+bx\),则待定系数法解方程可以确定:
\[\begin{cases}
a=\dfrac{x_j\cdot y_i-x_i\cdot y_j}{x_i\cdot x_j(x_i-x_j)}\\
b=\dfrac{y_i}{x_i}-a\cdot x_i
\end{cases}
\]
那么就可以确定哪些猪会被 \(\textit{line}_{i,j}\) 对应抛物线射死。
需要注意的是:
- 计算 \(\textit{line}_{i,j}\) 时需要特判 \(i=j\) 时:此时其状态 \(s\) 只包含 \(i\),即只射死猪 \(i\)。
- 算出来的二次函数 \(y=ax^2+bx\) 需要满足 \(a<0\),否则不是合法抛物线。
还需要注意的是,double
具有误差,因此当两个 double
只差的绝对值小于一定常数的时候,就应当判断其相等;这个常数一般称作 eps
,此处取 \(10^{-6}\)。
对于 DP 的转移,由 \(s\) 找到哪些抛物线是新增的显然不太好找,因此可以从已知状态 \(s\) 开始向其他状态递推。
即:
\[\textit{dp}_{s\cup\textit{line}_{i,j}}\leftarrow\min(\textit{dp}_{s\cup\textit{line}_{i,j}},\textit{dp}_s+1)
\]
其中,\(\leftarrow\) 表示赋值。当然也有代码版的递推方程:
dp[s|line[i][j]]=min(dp[s|line[i][j]],dp[s]+1);
AC 代码
时间复杂度:\(\mathcal O\left(Tn^2\log n\right)\)。
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=18;
constexpr const double eps=1e-6;
int n,dp[1<<N|1],line[N+1][N+1];
struct node{
double x,y;
}a[N+1];
struct func{
double a,b;
};
func calc(node a,node b){
double& x1=a.x,x2=b.x,y1=a.y,y2=b.y;
func ans;
ans.a=(x2*y1-x1*y2)/(x1*x2*(x1-x2));
ans.b=y1/x1-ans.a*x1;
return ans;
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;
cin>>T;
while(T--){
int m;
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>a[i].x>>a[i].y;
}
memset(line,0,sizeof(line));
for(int i=0;i<n;i++){
line[i][i]|=(1<<i);
for(int j=0;j<n;j++){
if(i==j){
continue;
}
auto pl=calc(a[i],a[j]);
if(pl.a>=0){
continue;
}
for(int k=0;k<n;k++){
if(abs(a[k].y-(pl.a*a[k].x*a[k].x+pl.b*a[k].x))<=eps){
line[i][j]|=(1<<k);
}
}
}
}
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(int i=0;i<(1<<n);i++){
for(int j=0;j<n;j++){
for(int k=0;k<n;k++){
dp[i|line[j][k]]=min(dp[i|line[j][k]],dp[i]+1);
}
}
}
cout<<dp[(1<<n)-1]<<'\n';
}
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}