当前位置: 代码迷 >> 综合 >> 【洛谷】【状压DP】P2831 愤怒的小鸟
  详细解决方案

【洛谷】【状压DP】P2831 愤怒的小鸟

热度:53   发布时间:2023-11-21 06:41:45.0

洛谷 P2831 愤怒的小鸟

题目大意

◇题目传送门◆

分析

看到NNN这么小,我们很容易想到状压或者是爆搜。。。

l(i,j)l(i,j)l(i,j)为经过猪iii和猪jjj的抛物线能够击中的猪的集合。

f(S)f(S)f(S)为击中SSS中的猪所需的最少的鸟的数量。则有以下两种转移:

  • 只发射击中猪iii的鸟:f(S∪{i})=min?(f(S)+1)f(S\cup \{i\})=\min(f(S) + 1)f(S{ i})=min(f(S)+1)
  • 找另外一只猪jjj,发射从猪iii到猪jjj的鸟,击中线上的所有点:f(S∪l(i,j))=min?(f(S)+1)f(S\cup l(i,j))=\min(f(S)+1)f(Sl(i,j))=min(f(S)+1)

但这样的复杂度是O(TN22N)O(TN^22^N)O(TN22N)的,这样做是过不了的,所以必须考虑优化。

考虑第一只不属于集合SSS中的猪iii,我们强制必须首先击中这只猪,然后转移。

因为若我们不击中这只猪,后面就必须再倒回来射这只猪,也就是后面没射的猪的转移都是多余的。所以这样转移就可以节省时间。

这样加上一个小的优化,复杂度就能够减少到O(TN2N)O(TN2^N)O(TN2N)了,就能够通过这道题。

参考代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;const int Maxn = 18;
const int INF = 0x3f3f3f3f;
const double EPS = 1e-6;int N;
double X[Maxn + 5], Y[Maxn + 5];
int line[Maxn + 5][Maxn + 5];
int f[(1 << Maxn) + 5];inline void solve(double &x, double &y, double a1, double b1, double c1,double a2, double b2, double c2) {
    y = (a1 * c2 - a2 * c1) / (a1 * b2 - a2 * b1);x = (c1 - b1 * y) / a1;
}int main() {
    
#ifdef LOACLfreopen("in.txt", "r", stdin);freopen("out.txt", "w", stdout);
#endifint _;scanf("%d", &_);while(_--) {
    scanf("%d %*d", &N);for(int i = 1; i <= N; i++)scanf("%lf %lf", &X[i], &Y[i]);memset(line, 0, sizeof line);memset(f, 0x3f, sizeof f);for(int i = 1; i <= N; i++)for(int j = 1; j <= N; j++) {
    if(fabs(X[i] - X[j]) < EPS) continue;double a, b;solve(a, b, X[i] * X[i], X[i], Y[i], X[j] * X[j], X[j], Y[j]);if(a > -EPS) continue;for(int k = 1; k <= N; k++)if(fabs(a * X[k] * X[k] + b * X[k] - Y[k]) < EPS)line[i][j] |= (1 << (k - 1));}f[0] = 0;for(int s = 0; s < (1 << N); s++)for(int i = 1; i <= N; i++)if(!(s & (1 << (i - 1)))) {
    f[s | (1 << (i - 1))] = min(f[s | (1 << (i - 1))], f[s] + 1);for(int j = 1; j <= N; j++) {
    if(fabs(X[i] - X[j]) < EPS) continue;f[s | line[i][j]] = min(f[s | line[i][j]], f[s] + 1);}break;}printf("%d\n", f[(1 << N) - 1]);}return 0;
}