题目描述
Kiana 最近沉迷于一款神奇的游戏无法自拔。
简单来说,这款游戏是在一个平面上进行的。有一架弹弓位于 (0, 0) 处,每次 Kiana 可以用它向第一象限发射一只小鸟,小鸟们的飞行轨迹均为形如 y = ax2 + bx 的曲线,其中 a,b 是 Kiana 指定的参数,且必须满足 a < 0。当小鸟落回地面(即 x 轴)时,它就会瞬间消失。
在游戏的某个关卡里,平面的第一象限中有 n 只猪,其中第 i 只猪所在的坐标为 (xi, yi)。如果某只小鸟的飞行轨迹经过了(xi, yi),那么第 i 只猪就会被消灭掉,同时小鸟将会沿着原先的轨迹继续飞行;如果一只小鸟的飞行轨迹没有经过(xi, yi),那么这只小鸟飞行的全过程就不会对第 i 只猪产生任何影响。
例如,若两只猪分别位于 (1, 3) 和 (3, 3),Kiana 可以选择发射一只飞行轨迹为 y = -x2 + 4x 的小鸟,这样两只猪就会被这只小鸟一起消灭。
而这个游戏的目的,就是通过发射小鸟消灭所有的猪。
这款神奇游戏的每个关卡对来说都很难,所以 Kiana 还输入了一些神秘的指令,使得自己能更轻松地完成这个游戏。这些指令将在「输入描述」中详述。
假设这款游戏一共有 T 个关卡,现在 Kiana 想知道,对于每一个关卡,至少需要发射多少只小鸟才能消灭所有的猪。由于她不会算,所以希望由你告诉她。
输入描述:
第一行包含一个正整数 T,表示游戏的关卡总数。
下面依次输入这 T 个关卡的信息。每个关卡第一行包含两个非负整数 n,m,分别表示该关卡中的猪数量和 Kiana 输入的神秘指令类型。
接下来的 n 行中,第 i 行包含两个正实数 (xi, yi),表示第 i 只猪坐标为 (xi, yi)。数据保证同一个关卡中不存在两只坐标完全相同的猪。
如果 m = 0,表示 Kiana 输入了一个没有任何作用的指令。
如果 m = 1,则这个关卡将会满足:至多用只小鸟即可消灭所有猪。
如果 m = 2,则这个关卡将会满足:一定存在一种最优解,其中有一只小鸟消灭了至少 只猪。
保证 1 ≤ n ≤ 18,0 ≤ m ≤ 2,0 < xi, yi < 10,输入中的实数均保留到小数点后两位。
上文中,符号 和 分别表示对 x 向上取整和向下取整。
输出描述:
对每个关卡依次输出一行答案。
输出的每一行包含一个正整数,表示相应的关卡中,消灭所有猪最少需要的小鸟数量。
示例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
1
1
这组数据中一共有两个关卡。
第一个关卡与「题目描述」中的情形相同,2 只猪分别位于 (1.00, 3.00) 和 (3.00, 3.00),只需发射一只飞行轨迹为 y = -x2 + 4x 的小鸟即可消灭它们。
第二个关卡中有 5 只猪,但经过观察我们可以发现它们的坐标都在抛物线 y = -x2 + 6x 上,故 Kiana 只需要发射一只小鸟即可消灭所有猪。
示例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
2
2
3
示例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
6
备注
测试点 1 ~ 14:2 ≤ n ≤ 12, 1 ≤ T ≤ 30;
测试点 15 ~ 20:2 ≤ n ≤ 18, 1 ≤ T ≤ 5。
解答
其实还是一个非常裸的状态压缩Dp,这次NOIP看似整体难度非常高,但实际上只是把题目难度次序重新编排了一下,导致大多数人被坑死在第二题。第三题都是非常良心的Dp,如果不给第二题,应该会有许多人都可以想到两天T3的正解。
这个n这么小,最大只有18,所以90%跟二进制有关系,事实上这个推断总是成立的。不知道二进制Dp的出门左转Baidu。
设dp[S]表示已经打了的猪的的序号组成的集合,那么我们枚举一个i表示这一次要打个猪,然后再枚举一个j,表示这一次把i和j一起打掉,那么预处理一个bit数组使得bit[i][j]表示以i和j的坐标确定的抛物线可以打掉的所有的猪,那么就由dp[S]+1转移到了dp[S|bit[i][j]]了,最后的答案就是.
说具体一点,就是用二进制枚举一个集合,比如1~3的子集可以用000,001,010,011,100,101,110,111这些二进制数来表示,从后往前数,如果一位是1表示这个数在这个子集中,0则表示不在。这样,集合的交并补就可以用二进制的位运算来表示了。&去交,|去并,~取补。
那么我们的dp是以集合为状态的,就用二进制搞一搞啦!
最后,还有一个小的常数优化,就是我们没有必要枚举所有的i,其实不论如何,在S中第一个没有出现的猪,我们最后一定要打的,所以我们干脆就只枚举最后那个猪,这样可以快一点哟。
参考程序:
#include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #define re register #define REP(i,a,b) for (re int i=(a);i<=(b);++i) using namespace std; template<class T>inline void ChkMin(T &a,T b){if (b<a)a=b;} typedef double db; const db eps=1e-7; const int N=20; int n,m,bit[N][N],dp[1<<N]; db x[N],y[N]; int main(){ int T; scanf("%d",&T); while (T--){ scanf("%d%d",&n,&m); REP(i,1,n)scanf("%lf%lf",&x[i],&y[i]); REP(i,1,n)REP(j,i+1,n){ db f=x[i]*x[j]*(x[i]-x[j]); db a=x[j]*y[i]-x[i]*y[j]; db b=x[i]*x[i]*y[j]-x[j]*x[j]*y[i]; bit[i][j]=0; if (a*f<0){ REP(k,1,n) if (fabs(a*x[k]*x[k]+b*x[k]-f*y[k])<eps)bit[i][j]|=1<<(k-1);//这是解二元二次方程,加减消原搞一搞就好了。 } } memset(dp,0x3f,sizeof(dp)); dp[0]=0; REP(k,0,(1<<n)-1){ int i=1; while ((k>>(i-1))&1)i++; //找到最小的那只猪 ChkMin(dp[(1<<(i-1))|k],dp[k]+1); //只单独打掉这只猪 REP(j,i+1,n)ChkMin(dp[k|bit[i][j]],dp[k]+1); //把这只猪和另一只猪打掉(另一只猪枚举已经打过的就没有意义了,所以从i+1开始枚举) } printf("%d\n",dp[(1<<n)-1]); } return 0; }