题解:[NOIP2016 提高组] 愤怒的小鸟

题目传送门:[NOIP2016 提高组] 愤怒的小鸟 - 洛谷

思路构建

我们首先看这道题的数据规模,发现n≤18,那么很容易想到这道题可能用状压dp来做。

我们可以先思考一个问题,几个点可以确定一个二次函数。这显然是一个小学二年级初中生都知道的问题——3点确定一个二次函数。在这道题中,题目已经帮你找到了一个点(原点),所以我们其实只需要两个点就可以确定一个二次函数了。但题中还有一个限制,那就是a<0,所以我们任意选取的两点不一定符合题意,所以我们得特判a是否为负数。

基于上面的思考,我们可以得到一种记录基本状态的思路——任选两个点进行枚举,得到一个满足题意的解析式(即a<0),然后再枚举剩下的点,找到所有在该二次函数上的点并用一个二进制数记录这个状态。

接着考虑dp过程。我们知道达到所有基本状态的最小步数是1,所以我们就可以在记录基本状态的时候将该状态的dp值记为1。然后,可以发现状态的合并这一过程中,状态所对应的二进制数单调递增,所以从小到大依次枚举可以通过基本状态取并集得来的状态,再用它的dp值来递推后面状态的值,最后输出全集的dp值即可。

细节思考

在代码实现里有几个小细节需要注意:

1.函数解析式的求法:

\begin{cases} & \text{} y_{1}= ax_{1}^{2}+bx_{1}\\ & \text{} y_{2}= ax_{2}^{2}+bx_{2} \end{cases} 

其中只有a,b是未知量。所以我们可以将这个方程转化为

\begin{cases} & \text{} y_{1}= ax_{11}+bx_{12}\\ & \text{} y_{2}= ax_{21}+bx_{22} \end{cases}

消元可得:

\begin{cases} & \text{} a= \frac{y_{1}*x_{22}-y_{2}*x_{12}}{x_{11}*x_{22}-x_{21}*x_{12}}\\ & \text{}b= \frac{y_{1}*x_{21}-y_{2}*x_{11}}{x_{12}*x_{21}-x_{22}*x_{11}} \end{cases}

2.基本状态只能记录1次,且不能互相为子集,因为如1,2,3都在一条二次函数上,那么只有2,3这个状态是不合法的。所以我们枚举两个点的时候i:1—>n-1,j:i+1—>n,并且已经和i一起记录过的点不能再枚举。

3.关于精度的问题。个人实测,精度设为1e-6可过。

代码实现

#include<bits/stdc++.h>
using namespace std;
int n,T,g[20],zt[410],cnt=0,f[1<<19];
struct point{
	double x,y;
}p[20];
inline void solve(double x11,double x12,double y1,double x21,double x22,double y2,double&a,double&b){
	a=(y1*x22-y2*x12)/(x11*x22-x21*x12);
	b=(y1*x21-y2*x11)/(x12*x21-x22*x11);
}//求解析式 
int main(){
	ios::sync_with_stdio(0);
	cin>>T;
	while(T--){
		memset(g,0,sizeof(g));
		memset(p,0,sizeof(p));
		memset(zt,0,sizeof(zt));
		memset(f,0x3f,sizeof(f));
		cnt=0;
		int m;
		cin>>n>>m;
		for(int i=1;i<=n;i++)
			cin>>p[i].x>>p[i].y;
		for(int i=1;i<n;i++){
			for(int j=i+1;j<=n;j++){
				int tmp=0;
				if((1<<(j-1))&g[i])continue;//保证两个点只能共同记录一次 
				double a=0,b=0;
				if(p[i].x!=p[j].x)solve(p[i].x*p[i].x,p[i].x,p[i].y,p[j].x*p[j].x,p[j].x,p[j].y,a,b);
				if(a>=0)continue;
				tmp|=1<<(i-1);tmp|=1<<(j-1);
				for(int k=n;k>j;k--){
					if(abs((p[k].x*p[k].x*a+p[k].x*b)-p[k].y)<=1e-6){
						tmp|=1<<(k-1);
						g[k]|=tmp;
					}
				}
				g[j]|=tmp;g[i]|=tmp;
				zt[++cnt]=tmp;
				f[tmp]=1;
			}
		}
		for(int i=1;i<=n;i++){
			if(!g[i]){zt[++cnt]=1<<(i-1);f[zt[cnt]]=1;}
		}
		int s=(1<<n)-1;
		for(int i=1;i<=s;i++){
			if(f[i]>=1000)continue;
			for(int j=1;j<=cnt;j++){
				f[i|zt[j]]=min(f[i|zt[j]],f[i]+1);
			}
		}
		cout<<f[s]<<'\n';
	}
	return 0;
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值