当前位置: 首页 > news >正文

P2831 [NOIP 2016 提高组] 愤怒的小鸟 题解

传送门

洛谷

题目大意

每关给你最多18只小猪(后文皆为18只),问你最少用几条过原点抛物线全部干掉。
注意这里 \(m\) 其实没用,因为你要是会算最优解了为啥还需要部分分啊?

思路

\(n\leq18\) ,不是暴搜就是状压。直接放弃暴搜。
状压dp做法:

1. 处理抛物线

首先记录这18只小猪所有可能产生的抛物线,如图所示为例1的第一组样例
image
注意这里“所有”可能产生的抛物线,包括只经过一只小猪(* ̄(oo) ̄) 的抛物线。
这个时候就有人会问,为了记录抛物线,需要记录 \(y=ax^2+bx\) 中的 \(a,b\) ,那要是记录只经过一只小猪的抛物线,还得考虑不经过其他小猪,岂不是要算废。
其实解决方法很简单,我们对于每条抛物线不记录两个存在误差的浮点 \(a,b\) 而是直接用二进制下的18位整数状压这条抛物线经过的小猪,可以证明对每只小猪一定存在只过它一点的抛物线。
记录只有一个点的抛物线很重要,我第一遍没有搞,结果过了样例,导致因为这个问题调了好久,有时还纳闷这个dp有没有真的考虑所有情况。

然后就是考虑过多个小猪的抛物线。先枚举第一只小猪,然后枚举下标在第一只小猪之后的小猪。计算出过这两只猪的抛物线,然后看其他小猪是否在此抛物线上,记录在对应的抛物线数组里即可。
代码实现:

#include <bits/stdc++.h>
using namespace std;
typedef pair<double, double> pdd;
const int N = 18, ZT = 1 << N;
const double eps = 1e-6; // 处理浮点数相等的精度问题, 凡差小于epsilon即算相等
int n, m;
double x[N], y[N];
int para[2000], cnt; // records parabolas// 计算过两点的抛物线
pdd calc(double x1, double y1, double x2, double y2)
{pdd res;double &a = res.first, &b = res.second;a = (x1 * y2 - x2 * y1) / (x1 * x2 * (x2 - x1));b = (x2 * x2 * y1 - x1 * x1 * y2) / (x1 * x2 * (x2 - x1));// printf("The parabola that goes through (%lf, %lf) and (%lf, %lf) is:\n\ty=%lfx^2+%lfx\n", x1, y1, x2, y2, a, b);return res;
}
void solve()
{// pre-calculate the parabolasfor (int i = 0; i < n; ++i) {para[cnt++] = (1 << i); // 只打小猪i的抛物线int vst = 0;for (int j = i + 1; j < n; ++j) {if (vst & (1 << j)) continue;// if (x[i] == x[j]) continue; // 防止calc函数出现除以0,但是好像不加也没影响照样ACpdd tmp = calc(x[i], y[i], x[j], y[j]);if (tmp.first >= 0) continue; // 题面要求抛物线开口向下,这不是数学,是物理!para[cnt] = (1 << i);for (int k = j; k < n; ++k) { // 这里k从j开始枚举,或者直接para[cnt]|=(1<<j),然后这里j+1开始枚举也没问题// printf("trying the parabola on (%lf, %lf): ", x[k], y[k]);double res = tmp.first * x[k] * x[k] + tmp.second * x[k];// printf("result is %lf\n", res);if (abs(res - y[k]) <= eps) {// printf("Success! It IS on the parabola!\n");vst |= (1 << k); // 防止再次枚举到过小猪 i,j,k的抛物线para[cnt] |= (1 << k);}}++cnt;}}// for (int i = 0; i < cnt; ++i) {// 	printf("parabola %d goes through these pigs: ", i);// 	for (int j = 0; j < n; ++j) {// 		if (para[i] & (1 << j)) {// 			printf("%d ", j);// 		}// 	}// 	printf("\n");// }
}

注意抛物线数组开大点,不要以为抛物线最多只有18条,我们记录的是每一条可能的抛物线。

2. dp

显然对于每个状态 \(i(0\leq i\leq 2^{n+1}-1)\) ,枚举每条抛物线 \(para_j\) 带来的影响,有:

\[\text{dp}[i|\text{para}_j]=\min(\text{dp}[i|\text{para}_j],\,\text{dp}[i]+1) \]

至此,已成艺术完结撒花。

代码(含调试+原版注释)

#include <bits/stdc++.h>
using namespace std;
typedef pair<double, double> pdd;
const int N = 18, ZT = 1 << N;
const double eps = 1e-6;
int n, m;
double x[N], y[N];
int para[2000], cnt; // records parabolas
int dp[ZT];
pdd calc(double x1, double y1, double x2, double y2)
{pdd res;double &a = res.first, &b = res.second;a = (x1 * y2 - x2 * y1) / (x1 * x2 * (x2 - x1));b = (x2 * x2 * y1 - x1 * x1 * y2) / (x1 * x2 * (x2 - x1));// printf("The parabola that goes through (%lf, %lf) and (%lf, %lf) is:\n\ty=%lfx^2+%lfx\n", x1, y1, x2, y2, a, b);return res;
}
void solve()
{cnt = 0;cin >> n >> m;for (int i = 0; i < n; ++i) {cin >> x[i] >> y[i];}// pre-calculate the parabolasfor (int i = 0; i < n; ++i) {para[cnt++] = (1 << i); // 只打小猪i的抛物线int vst = 0;for (int j = i + 1; j < n; ++j) {if (vst & (1 << j)) continue;// if (x[i] == x[j]) continue;// printf("pig %d and pig %d: ", i, j);pdd tmp = calc(x[i], y[i], x[j], y[j]);if (tmp.first >= 0) continue;para[cnt] = (1 << i);for (int k = j; k < n; ++k) {// printf("trying the parabola on (%lf, %lf): ", x[k], y[k]);double res = tmp.first * x[k] * x[k] + tmp.second * x[k];// printf("result is %lf\n", res);if (abs(res - y[k]) <= eps) {// printf("Success! It IS on the parabola!\n");vst |= (1 << k);para[cnt] |= (1 << k);}}++cnt;}}// for (int i = 0; i < cnt; ++i) {// 	printf("parabola %d goes through these pigs: ", i);// 	for (int j = 0; j < n; ++j) {// 		if (para[i] & (1 << j)) {// 			printf("%d ", j);// 		}// 	}// 	printf("\n");// }const int FULL = (1 << n) - 1;for (int i = 1; i <= FULL; ++i) dp[i] = 30;for (int i = 0; i <= FULL; ++i) {for (int j = 0; j < cnt; ++j) {dp[i | para[j]] = min(dp[i | para[j]], dp[i] + 1);}}cout << dp[FULL] << '\n';
}
int main()
{// freopen("F.in", "r", stdin);// freopen("F.out", "w", stdout);ios::sync_with_stdio(false);cin.tie(nullptr);int T;cin >> T;for (int i = 1; i <= T; ++i) {// printf("########### TEST CASE %d ###########\n", i);solve();// printf("\n");}return 0;
}

代码(无//)

#include <bits/stdc++.h>
using namespace std;
typedef pair<double, double> pdd;
const int N = 18, ZT = 1 << N;
const double eps = 1e-6;
int n, m;
double x[N], y[N];
int para[2000], cnt;
int dp[ZT];
pdd calc(double x1, double y1, double x2, double y2)
{pdd res;double &a = res.first, &b = res.second;a = (x1 * y2 - x2 * y1) / (x1 * x2 * (x2 - x1));b = (x2 * x2 * y1 - x1 * x1 * y2) / (x1 * x2 * (x2 - x1));return res;
}
void solve()
{cnt = 0;cin >> n >> m;for (int i = 0; i < n; ++i) {cin >> x[i] >> y[i];}for (int i = 0; i < n; ++i) {para[cnt++] = (1 << i);int vst = 0;for (int j = i + 1; j < n; ++j) {if (vst & (1 << j)) continue;pdd tmp = calc(x[i], y[i], x[j], y[j]);if (tmp.first >= 0) continue;para[cnt] = (1 << i);for (int k = j; k < n; ++k) {double res = tmp.first * x[k] * x[k] + tmp.second * x[k];if (abs(res - y[k]) <= eps) {vst |= (1 << k);para[cnt] |= (1 << k);}}++cnt;}}const int FULL = (1 << n) - 1;for (int i = 1; i <= FULL; ++i) dp[i] = 30;for (int i = 0; i <= FULL; ++i) {for (int j = 0; j < cnt; ++j) {dp[i | para[j]] = min(dp[i | para[j]], dp[i] + 1);}}cout << dp[FULL] << '\n';
}
int main()
{ios::sync_with_stdio(false);cin.tie(nullptr);int T;cin >> T;for (int i = 1; i <= T; ++i) solve();return 0;
}

别跑,还有压行版

#include<bits/stdc++.h>
using namespace std;typedef pair<double,double>pdd;const int N=18,ZT=1<<N;const double eps=1e-6;int n,m;double x[N],y[N];int para[2000],cnt;int dp[ZT];pdd calc(double x1,double y1,double x2,double y2){pdd res;double&a=res.first,&b=res.second;a=(x1*y2-x2*y1)/(x1*x2*(x2-x1));b=(x2*x2*y1-x1*x1*y2)/(x1*x2*(x2-x1));return res;}void solve(){cnt=0;cin>>n>>m;for(int i=0;i<n;++i){cin>>x[i]>>y[i];}for(int i=0;i<n;++i){para[cnt++]=(1<<i);int vst=0;for(int j=i+1;j<n;++j){if(vst&(1<<j))continue;pdd tmp=calc(x[i],y[i],x[j],y[j]);if(tmp.first>=0)continue;para[cnt]=(1<<i);for(int k=j;k<n;++k){double res=tmp.first*x[k]*x[k]+tmp.second*x[k];if(abs(res-y[k])<=eps){vst|=(1<<k);para[cnt]|=(1<<k);}}++cnt;}}const int FULL=(1<<n)-1;for(int i=1;i<=FULL;++i)dp[i]=30;for(int i=0;i<=FULL;++i){for(int j=0;j<cnt;++j){dp[i|para[j]]=min(dp[i|para[j]],dp[i]+1);}}cout<<dp[FULL]<<'\n';}int main(){ios::sync_with_stdio(false);cin.tie(nullptr);int T;cin>>T;for(int i=1;i<=T;++i)solve();return 0;}

总结

挺容易写的一道蓝题,没调太久。
算是自己想不出,一听到枚举抛物线就懂的题。

http://www.gsyq.cn/news/16420.html

相关文章:

  • MariaDB收购SkySQL增强AI与无服务器能力
  • 阿里云为何,一个邮箱绑定了两个账号 - 教程
  • 深入解析:【设计模式-3.5】结构型——装饰器模式
  • 阿爸阿爸
  • Python 数据分析与可视化实战:从数据清洗到图表呈现 - 指南
  • 【深度学习优化算法】02:凸性 - 详解
  • 调了很久的代码总结
  • 在Windows上搭建 EasyTier 公共服务器
  • ARC 207 (Div.1)
  • (转载)无人机飞行模式全面解析
  • LVS+Keepalived高可用群集 - 指南
  • uniapp 转回tabbar页面
  • JDK 离线安装
  • HbuilderX 将 h5转成uniapp的一些记录.19127294
  • 悟空博弈单元(WBUC)与广域统一计算(WAUC)研究:价值共生的技术基石——声明Ai研究
  • 掌握形式验证工具,提升芯片验证效率
  • P2724 [IOI 1998 / USACO3.1] 联系 Contact 做题笔记
  • 如果能重来
  • 版权诉讼下的MiniMax:AI独角兽的上市迷途
  • 手机照片太多了存哪里? - 实践
  • 高贵的北上广深,没有父母托举,90后很难成家
  • 安装iTrustSSL证书 去除此网站不支持安全连接提示
  • 2025钻机厂家最新推荐榜:岩芯钻机,勘探钻机,地质钻机,取样钻机,空气反循环钻机公司推荐
  • iNaturalist开放自然数据与计算机视觉挑战
  • [Node.js] Server-Sent Events
  • Software Foundations Vol.I : 归纳证明(Induction)
  • CF2152G Query Jungle(线段树,重链剖分,*)
  • 代码随想录算法训练营第九天 | leetcode 151 卡特55
  • [题解] 分竹子
  • 实力强劲的机器视觉公司有哪些:2025年TOP5精选榜单