洛谷P2831 愤怒的小鸟
状压DP。
直接枚举二进制数表示当前猪有没有被消灭的状态。
最终答案的几条抛物线必定至少撞到一个猪。而且两头猪确定一条抛物线,可以枚举两头猪,分别求出他们的抛物线所消灭猪的状态,然后可以用类似背包的方法转移DP。
有方程:
\(dp[i|(\)当前抛物线的状态\()] = min(dp[i|(\)当前抛物线的状态\()],dp[i]+1)\)
有转移:
枚举每个状态,每个状态再枚举每个可以撞到的一头猪,再用该猪枚举是否存在既能撞到该猪,又能撞到另一头猪的抛物线,然后即可确定抛物线,也可确定抛物线的状态了。根据抛物线的状态,即可实现转移
#include <bits/stdc++.h>
#define N 1001001
using namespace std;
int T, n, m;
double x[1001], y[1001];
int vis[1001000], dp[1010011], ha[1001][1001];
double F(double a, double b, double x)
{
double y = a * x * x + b * x;
return y;
}
inline void add(int i, int j)//确定好所有抛物线的状态
{
if (abs(x[i] - x[j]) <= 0.0000001) return;//判断是否两个点重合
double b = (x[i] * x[i] * y[j] - x[j] * x[j] * y[i]) / (x[i] * x[i] * x[j] - x[j] * x[j] * x[i]);
double a = (y[i] - b * x[i]) / (x[i] * x[i]);//两点确定一条抛物线。
if (a < 0)//a必须小于0
{
ha[i][j] = (1 << i - 1) | (1 << j - 1);
for (int k = 1; k <= n; k++)
if (abs(F(a, b, x[k]) - y[k]) <= 0.0000001 && k != i && k != j)
ha[i][j] |= (1 << k - 1);//表示状态+=(1<<k-1) 用二进制表示状态。
}
}
inline void clear()
{
memset(dp, 0x3f, sizeof(dp));
memset(vis, 0, sizeof(vis));
memset(ha, 0, sizeof(ha));
dp[0] = 0;
}
int main()
{
scanf("%d", &T);
while (T--)
{
clear();
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%lf%lf", &x[i], &y[i]);//考虑搜索
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
add(i, j);//求出i,j确定的抛物线状态
for (int i = 0; i < (1 << n); i++)//当前状态是i
for (int j = 1; j <= n; j++)//给当前状态加上一条抛物线,首先必定会撞到1个点,
{
dp[i | (1 << j - 1) ] = min(dp[i | (1 << j - 1)], dp[i] + 1);
for (int k = j + 1; k <= n; k++)//再找一个能跟j在同一条抛物线上的点,然后加上此抛物线所确定的状态。
if (ha[j][k])
dp[i | ha[j][k]] = min(dp[i | ha[j][k]], dp[i] + 1);
}
printf("%d\n", dp[(1 << n)- 1]);//所有数都选上的最小代价。
}
return 0;
}