[NOIP 2016 Day2.T3][Luogu P2831] 愤怒的小鸟 搜索状压DP

mac2024-05-19  31

文章目录

题目描述样例输入输出题解搜索参考代码状压dp参考代码

题目描述

题目描述 Kiana 最近沉迷于一款神奇的游戏无法自拔。 简单来说,这款游戏是在一个平面上进行的。 有一架弹弓位于 (0,0) 处,每次 Kiana 可以用它向第一象限发射一只红色的小鸟,小鸟们的飞行轨迹均为形如 y=ax^2+bx的曲线,其中 a,b是Kiana 指定的参数,且必须满足 a<0,a,b 都是实数。 当小鸟落回地面(即 x 轴)时,它就会瞬间消失。 在游戏的某个关卡里,平面的第一象限中有 n 只绿色的小猪,其中第 i 只小猪所在的坐标为 (xi​,yi​) 。 如果某只小鸟的飞行轨迹经过了 (xi​,yi​) ,那么第 i 只小猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行; 如果一只小鸟的飞行轨迹没有经过 (xi​,yi​) ,那么这只小鸟飞行的全过程就不会对第 i 只小猪产生任何影响。 例如,若两只小猪分别位于 (1,3) 和 (3,3) ,Kiana 可以选择发射一只飞行轨迹为 y=-x^2+4x 的小鸟,这样两只小猪就会被这只小鸟一起消灭。 而这个游戏的目的,就是通过发射小鸟消灭所有的小猪。 这款神奇游戏的每个关卡对 Kiana来说都很难,所以Kiana还输入了一些神秘的指令,使得自己能更轻松地完成这个游戏。这些指令将在【输入格式】中详述。 假设这款游戏一共有 T 个关卡,现在 Kiana想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的小猪。由于她不会算,所以希望由你告诉她。

输入格式 第一行包含一个正整数 T ,表示游戏的关卡总数。 下面依次输入这 T 个关卡的信息。每个关卡第一行包含两个非负整数 n,mn,m ,分别表示该关卡中的小猪数量和 Kiana 输入的神秘指令类型。接下来的 n 行中,第 ii 行包含两个正实数 xi​,yi​ ,表示第 i 只小猪坐标为 (xi​,yi​)。数据保证同一个关卡中不存在两只坐标完全相同的小猪。 如果 m=0 ,表示Kiana输入了一个没有任何作用的指令。 如果 m=1 ,则这个关卡将会满足:至多用 ⌈n/3+1⌉ 只小鸟即可消灭所有小猪。 如果 m=2 ,则这个关卡将会满足:一定存在一种最优解,其中有一只小鸟消灭了至少 ⌊n/3⌋ 只小猪。 保证 1≤n≤18 , 0≤m≤2 , 0<xi​,yi​<10 ,输入中的实数均保留到小数点后两位。 上文中,符号 ⌈c⌉ 和 ⌊c⌋ 分别表示对 c 向上取整和向下取整,例如: ⌈2.1⌉=⌈2.9⌉=⌈3.0⌉=⌊3.0⌋=⌊3.1⌋=⌊3.9⌋=3 。

输出格式 对每个关卡依次输出一行答案。 输出的每一行包含一个正整数,表示相应的关卡中,消灭所有小猪最少需要的小鸟数量。

数据范围及提示

样例输入输出

Sample Input 1 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 Sample Output 1 1 1 样例解释 1 这组数据中一共有两个关卡。 第一个关卡与【问题描述】中的情形相同,2只小猪分别位于(1.00,3.00)和 (3.00,3.00),只需发射一只飞行轨迹为y = -x^2 + 4x的小鸟即可消灭它们。 第二个关卡中有5只小猪,但经过观察我们可以发现它们的坐标都在抛物线 y = -x^2 + 6x上,故Kiana只需要发射一只小鸟即可消灭所有小猪。

Sample Input 2 3 2 0 1.41 2.00 1.73 3.00 3 0 1.11 1.41 2.34 1.79 2.98 1.49 5 0 2.72 2.72 2.72 3.14 3.14 2.72 3.14 3.14 5.00 5.00 Sample Output 2 2 2 3

Sample Input 3 1 10 0 7.16 6.28 2.02 0.38 8.33 7.78 7.68 2.09 7.46 7.86 5.77 7.44 8.24 6.72 4.42 5.11 5.42 7.79 8.15 4.99 Sample Output 3 6

题解

搜索

看了一眼数据范围,锁定搜索 + 剪枝 搜索就很简单粗暴了,首先是枚举两头猪,锁定一条抛物线,然后去找哪些猪还活着,就去把它kill掉。。。(很简单吧,我也jio得,就是不会码)

搜索的时候,对于每头猪,有以下两种情况: ①.该猪还活着:   a. 让它继续活着吧   b. 让它再找一只猪作伴,去阴间相会    对于这种情况,需要保证另一只猪尚还安在,因此,我们需要在前面已经算过    的猪中给它找伴 ②.该猪已经被之前的鸟射中了:   那就让他安静地死去吧,不管了 那么,我们就可以码出下面这份代码了(具体操作代码解释)。

参考代码

#include <cstdio> #include <iostream> #include <cmath> using namespace std; const int N = 18; const double esp = 1e-8; int T, n, m, ans; double x[N + 5], y[N + 5]; double aliveX[N + 5], aliveY[N + 5], paraA[N + 5], paraB[N + 5]; inline double countY (const double a, const double b, const double X) { return a * X * X + b * X; } inline bool checkSame (const double X, const double Y) { return fabs (X - Y) < esp; } //now__: 当考虑到第now__头猪; curve:已经有了curve条抛物线; // restPig:当前情况下,now__以前的猪还活着restPig头. void sovle (const int now__, const int curve, const int restPig) { if (ans <= curve + restPig) return ; if (now__ == n + 1) { ans = restPig + curve; return ; } bool killed = false; for (int i = 1; i <= curve; ++ i) { if (checkSame ( countY (paraA[i], paraB[i], x[now__]), y[now__] )) { killed = true; sovle (now__ + 1, curve, restPig); break; } } //判断是否在前面已经被鸟射中了 if (! killed) { for (int i = 1; i <= restPig; ++ i) { if ( checkSame (aliveX[i], x[now__]) ) continue; //处于同一个横坐标的猪不可能被同一只鸟射中 //拉一头猪陪它狗带 double x1 = x[now__], x2 = aliveX[i]; int y1 = y[now__], y2 = aliveY[i]; double a = (y1 * x2 - y2 * x1) / (x1 * x1 * x2 - x1 * x2 * x2); double b = (y1 - x1 * x1 * a) / x1; if (a >= 0.0) continue; paraA[curve + 1] = a; paraB[curve + 1] = b; for (int j = i; j < restPig; ++ j) { aliveX[j] = aliveX[j + 1]; aliveY[j] = aliveY[j + 1]; } sovle (now__ + 1, curve + 1, restPig - 1); for (int j = restPig; j > i; -- j) { aliveX[j] = aliveX[j - 1]; aliveY[j] = aliveY[j - 1]; } aliveX[i] = x2; aliveY[i] = y2; } aliveX[restPig + 1] = x[now__]; aliveY[restPig + 1] = y[now__]; sovle (now__ + 1, curve, restPig + 1); //让它再多活一会儿 } } int main () { scanf ("%d", &T); while (T --) { scanf ("%d %d", &n, &m); for (int i = 1; i <= n; ++ i) scanf ("%lf %lf", &x[i], &y[i]); ans = 20; sovle (1, 0, 0); printf ("%d\n", ans); } return 0; }

状压dp

由于n的范围很小,所以除了搜索,这道题也可以用状压dp。(不过搜索比dp快好多。。。) 首先和大多是题目一样,我们用二进制来表示不同的状态(下面用1表示该猪已死,0表示该猪尚安在)。 假设当前状态为s,如果我们之前发射了一条可以射到s‘状态的猪的抛物线(语言有点绕,望谅解),那么,可以得到状态转移方程式: d p [ s ] = m i n ( d p [ s ^ ( s & s ′ ) ] + 1 ) dp[s] = min(dp[s^(s&s')] + 1) dp[s]=min(dp[s(ss)]+1) 稍微解释一下,s&s’表示同时出现在s和s’中已故的猪仔们,再用s异或s&s’表示从上一个状态新增一条抛物线后新增的故去发猪仔(如果有不懂位运算的,出门右拐问度娘 ☞ 友情链接,百度百科 )

参考代码

#include <cstdio> #include <iostream> #include <cmath> #include <cstring> using namespace std; const int N = 18; const double esp = 1e-8; int T, n, m, init[(1 << N) + 5], f[N + 5][N + 5], dp[(1 << N) + 5]; double x[N + 5], y[N + 5]; inline bool checkSame (const double X, const double Y) { return fabs (X - Y) <= esp; } int main () { for (int i = 1; i < (1 << N); ++ i) for (int j = 1; j <= N; ++ j) if (i & (1 << (j - 1))) { init[i] = j; break; } scanf ("%d", &T); while (T --) { scanf ("%d %d", &n, &m); for (int i = 1; i <= n; ++ i) scanf ("%lf %lf", &x[i], &y[i]); memset (f, 0, sizeof (f)); for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= n; ++ j) { if (x[i] == x[j]) continue; double x1 = x[i], x2 = x[j]; double y1 = y[i], y2 = y[j]; double a = (y1 * x2 - y2 * x1) / (x1 * x2 * (x1 - x2)); double b = (y1 - x1 * x1 * a) / x1; if (a > -esp) continue; for (int k = 1; k <= n; ++ k) if (checkSame (x[k] * x[k] * a + x[k] * b, y[k])) f[i][j] |= (1 << (k - 1)); } } //计算每两个点之间连线可以射到的猪仔 dp[0] = 0; for (int i = 1; i < 1 << n; ++ i) { dp[i] = dp[i ^ (1 << (init[i] - 1))] + 1; for (int j = 1; j <= n; ++ j) if (j != init[i] && i & (1 << (j - 1))) dp[i] = min ( dp[i], dp[i ^ (i & f[init[i]][j])] + 1 ); } printf ("%d\n", dp[(1 << n) - 1]); } return 0; }
最新回复(0)