【NOIP2016提高组】愤怒的小鸟

https://www.luogu.org/problem/show?pid=2831

BFS

看到N这么小就可以想到搜索,求最少步数显然应该用BFS

在这题中过两猪可以唯一确定一条抛物线,每一步可以发射两只猪确定的一条抛物线(打下这条抛物线上的所有猪),也可以发射一条只经过一只猪的抛物线(只打下这只猪)。

这时可以想到状压存储每个状态,并且读入所有猪时预处理一下每两只猪确定的抛物线。

两点确定一条过原点的抛物线y=ax2+bx的方法:点(x1, y1) (x2, y2)过抛物线,得y1=ax12+bx1,y2=ax22+bx2,两式分别变形得y1/x1-ax1=y2/x2-ax2=b,再整理得a=(y2/x2-y1/x1)/(x2-x1)。求出a后往回代可求出b。

记得记录每个状态是否已经搜索过,避免重复的状态入队。由于共有2n个状态,故时间复杂度是O(2n)的。如果不剪枝就是O(n!)。

题目给的m应该是用来xjb剪枝用的,但是m=2怎么用我也不懂……

注意事项:

  • 判断两个浮点数相等要考虑精度误差。
  • stl的queue不开优化会很慢,可以考虑手写队列。
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
#include <cmath>
using namespace std;
bool equal(double x, double y, double eps = 1e-6)
{
    return fabs(x - y) <= eps;
}
int n;
bool m[3];
pair<double, double> pigs[20];
unsigned target;       // 搜索的最终目标,即第1~n位均为1
unsigned pwxs[20][20]; // pwxs[i][j] => i、j两只猪所在抛物线上的所有猪
bool visited[(1 << 20)];
int bfs()
{
    typedef pair<unsigned, int> state; // <状态, 步数>
    queue<state> q;
    q.push(make_pair(0, 0));
    while (!q.empty())
    {
        state x = q.front();
        q.pop();

        // 用一只鸟打一只猪的情况
        for (int i = 1; i <= n; i++)
        {
            if (!(x.first & (1 << i))) // 如果i猪还没被打下
            {
                state y = x;
                y.first |= (1 << i);
                y.second++;
                if (y.first == target)
                    return y.second;
                if (!visited[y.first] && !(m[1] && y.second > (int)(n * 1.0 / 3 + 1)))
                {
                    q.push(y);
                    visited[y.first] = true;
                }
            }
        }

        // 用一只鸟打两只猪的情况
        for (int i = 1; i <= n; i++)
        {
            for (int j = i + 1; j <= n; j++)
            {
                if (!(x.first & (1 << i)) && !(x.first & (1 << j)))  // 如果i、j猪都还没被打下
                {
                    state y = x;
                    y.first |= pwxs[i][j];
                    y.second++;
                    if (y.first == target)
                        return y.second;
                    if (!visited[y.first] && !(m[1] && y.second > (int)(n * 1.0 / 3 + 1)))
                    {
                        q.push(y);
                        visited[y.first] = true;
                    }
                }
            }
        }
    }
    return -1;
}
int main()
{
    ios::sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--)
    {
        memset(visited, false, 1 << 20);

        int c;
        cin >> n >> c;
        m[c] = true;

        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                pwxs[i][j] = 0;

        target = 0;
        for (int i = 1; i <= n; i++)
            target |= (1 << i);

        double a, b;
        for (int i = 1; i <= n; i++)
        {
            cin >> a >> b;
            pigs[i] = make_pair(a, b);
        }

        for (int i = 1; i <= n; i++)
        {
            for (int j = i + 1; j <= n; j++)
            {
                /* ∵ y1=ax1^2+bx1, y2=ax2^2+bx2
                   ∴ b=y1/x1-ax1=y2/x2-ax2
                   ∴ a=(y2/x2-y1/x1)/(x2-x1) */
                double &x1 = pigs[i].first, &y1 = pigs[i].second;
                double &x2 = pigs[j].first, &y2 = pigs[j].second;
                a = (y2 / x2 - y1 / x1) / (x2 - x1);
                b = y1 / x1 - a * x1;
                if (a < 0)
                {
                    for (int k = 1; k <= n; k++)
                    {
                        double &x = pigs[k].first, &y = pigs[k].second;
                        if (equal(a * x * x + b * x, y))
                            pwxs[i][j] |= (1 << k);
                    }
                }
            }
        }
        cout << bfs() << endl;
    }
    return 0;
}

看到N这么小就可以想到搜索,求最少步数显然应该用BFS

在这题中过两猪可以唯一确定一条抛物线,每一步可以发射两只猪确定的一条抛物线(打下这条抛物线上的所有猪),也可以发射一条只经过一只猪的抛物线(只打下这只猪)。

这时可以想到状压存储每个状态,并且读入所有猪时预处理一下每两只猪确定的抛物线。

记得记录每个状态是否已经搜索过,避免重复的状态入队。

原文地址:https://www.cnblogs.com/ssttkkl/p/7124206.html