洛谷

洛谷题单指南-状态压缩动态规划-P2831 [NOIP 2016 提高组] 愤怒的小鸟

原题链接:https://www.luogu.com.cn/problem/P2831

题意解读:求覆盖平面上所有点的最少的曲线数,曲线形如y=ax2+bx

解题思路:

1、两点确定一条曲线

由于曲线方程为:y=ax2+bx,可以判断两点确定一条曲线

设两点为(x1,y1),(x2,y2),则有

y1 = ax12 + bx1,y2 = ax22 + bx2

解方程得到:a = (y1/x1-y2/x2)/(x1-x2),b = y1/x1 - ax1

可以枚举所有两个点构成的曲线,再将所有点代入方程即可确定每条曲线经过了哪些点,每条曲线经过的点可以用状态压缩存入一个整数

设path[i][j]表示第i、j个点构成的曲线经过的所有点的状态

这里有一个特殊情况,如果两个不同的点构成的曲线a不小于0则不合法,如果所有两点构成的曲线都不合法,经过每一个点至少有一个合法的曲线,也

就是path[i][i] = 1 << i,第i个点必有曲线经过i。

2、搜索

dfs(当前已覆盖的点:stat,用了几条曲线:cnt)

{

  if stat表示所有点已覆盖

    ans = min(ans, cnt)

    return

  for 所有当前没有覆盖的点

    对于任意一个没有覆盖的点,找到所有经过它的曲线

      用曲线经过的点的状态 | stat,得到newstat

      继续dfs(newstat, cnt + 1) 

    break,每次只找一个没有覆盖的点,用所有经过它的曲线去更新状态

}

调用:dfs(0, 0)

3、考虑到性能,可以定义f[i]记录已覆盖点状态为i时经过的最少曲线数,使用记忆化搜索

dfs(当前已覆盖的点:stat,用了几条曲线:cnt)

{

  if(f[i] != INF && f[i] <= cnt) return;

  f[i] = min(f[i], cnt)

  if stat表示所有点已覆盖

    ans = min(ans, cnt)

    return

  for 所有当前没有覆盖的点

    对于任意一个没有覆盖的点,找到所有经过它的曲线

      用曲线经过的点的状态 | stat,得到newstat

      继续dfs(newstat, cnt + 1) 

    break,每次只找一个没有覆盖的点,用所有经过它的曲线去更新状态

}

4、注意浮点数的比较不能直接用==,应该相减看是否小于一个更小的值

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 18, INF = 0x3f3f3f3f;
const double EPS = 1e-6;
struct Point
{ 
    double x, y; 
} points[N];
int path[N][N]; //path[i][j]表示i,j两个点构成的曲线经过的所有点的状态
int f[1 << N]; //f[i]表示经过的所有点的状态为i时所需的最少曲线数
int t, n, m, ans;

int cmp(double x, double y)
{
    if(fabs(x - y) < EPS) return 0;
    if(x > y) return 1;
    else if(x < y) return -1;
}

void dfs(int stat, int cnt)
{
    if(f[stat] <= cnt) return; //记忆化+最优性剪枝
    f[stat] = min(f[stat], cnt);
    if(stat == (1 << n) - 1) //所有点都已覆盖
    {
        ans = min(ans, cnt);
        return;
    }
    for(int i = 0; i < n; i++)
    {
        if(!(stat >> i & 1))
        {
            for(int j = 0; j < n; j++)
            {
                if(path[i][j])
                {
                    int newstat = stat | path[i][j];
                    dfs(newstat, cnt + 1);
                }
            }
            break; //每次取一个未被覆盖的点,用经过该点的不同曲线去更新覆盖状态
        }
    }
}

int main()
{
    cin >> t;
    while(t--)
    {
        memset(path, 0, sizeof(path));
        cin >> n >> m;
        for(int i = 0; i < n; i++)
        {
            cin >> points[i].x >> points[i].y;
        }
        for(int i = 0; i < n; i++)
        {
            path[i][i] = 1 << i; //每个点一定有一条经过它的曲线
            for(int j = 0; j < n; j++)
            {
                double x1 = points[i].x, y1 = points[i].y;
                double x2 = points[j].x, y2 = points[j].y;
                if(!cmp(x1, x2)) continue; //两点横坐标相同,必然不能形成经过原点的曲线
                double a = (y1 / x1 - y2 / x2) / (x1 - x2);
                double b = y1 / x1 - a * x1;
                if(cmp(a, 0.0) != -1) continue; //a必须小于0
                //找到曲线y=ax^2+bx经过的所有点
                for(int k = 0; k < n; k++)
                {
                    double x = points[k].x, y = points[k].y;
                    if(!cmp(y, a * x * x + b * x))
                    {
                        path[i][j] |= (1 << k);
                    }
                }
            }
        }
        ans = INF;
        memset(f, 0x3f, sizeof(f));
        dfs(0, 0);
        cout << ans << endl;
    }
    return 0;
}