当前位置: 首页 > 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.hskmm.com/?act=detail&tid=25347

相关文章:

  • 库存中心(三层库存模型)
  • Valley靶机渗透实战:从凭证复用到Python库劫持
  • 10.05模拟赛反思
  • MariaDB收购SkySQL增强AI与无服务器能力
  • 单片机寄存器的四种主要类型! - 实践
  • TDengine 高级特性——读缓存
  • 非合作博弈之软性均衡:东方智慧与西方理论的融合框架
  • 如何快速搭建spring-boot工程 - murphy
  • Ai元人文:东谈西论——非合作博弈之软性均衡
  • 反向传播与梯度下降:神经网络如何真正学会分类
  • Spring Cloud Alibaba微服务开发
  • OI 各种东西的板子
  • 价值弥漫:AI元人文的场域革命与共生之路
  • 做题记录 #1
  • 阿爸阿爸
  • 深度学习优化器算法巧思速览
  • 完整教程:LangChain完全指南:从入门到精通,打造AI应用开发新范式
  • NDK开发与实践(入门篇微课视频版)
  • 调了很久的代码总结
  • CF700E
  • 价值弥漫:“AI元人文”的场域革命与共生之路
  • k8s之pod概念
  • CF 1055 Div.1+Div.2
  • 2026 NOI 做题记录(五)
  • ARC 207 (Div.1)
  • “齐俊杰投资智能体”更新完了9月份的资料
  • LVS+Keepalived高可用群集 - 指南
  • luogu P1020 [NOIP 1999 提高组] 导弹拦截
  • RabbitMQ 离线安装
  • Nginx 离线安装