题解/算法 {5729. 闯关游戏}

题解/算法 {5729. 闯关游戏}

@LINK: https://www.acwing.com/problem/content/description/5732/;

很容易想到是哈密顿路径DP, 看一个错误代码

int N, M, K;
cin>> N>> M>> K;
vector<int> A(N);  for( auto & i : A){ cin>> i;}
int Edge[ 20][ 20];
std::memset( Edge, 0, sizeof(Edge));
FOR_( _, 1, K){
    int a, b, v;  cin>>a>>b>>v;  --a, --b;
    Edge[ a][ b] = v;
}

static Int64_ DP[ 1<<18][ 18];
Int64_ ANS = 0;
FOR_( i, 0, N-1){
    DP[1<<i][i] = A[i];
    if( 1 == M){
        ANS = max( ANS, (Int64_)A[i]);
    }
}
FOR_( X, 3, (1<<N)-1){
    for( auto tx = X; tx > 0; tx ^= (1<< ___Binary_LowBit_1(tx))){
        auto Y = ___Binary_LowBit_1(tx);
        auto & curDP = DP[X][Y];
        curDP = 0;
        int preX = (X ^ (1<<Y));
        for( auto ttx = preX; ttx > 0; ttx ^= (1<< ___Binary_LowBit_1(ttx))){
            auto preY = ___Binary_LowBit_1(ttx);
            curDP = std::max( curDP, DP[preX][preY] + Edge[preY][Y] + A[Y]);
        }
        if( __builtin_popcount( X) == M){
            ANS = max( ANS, curDP);
        }
    }
}
cout<< ANS;

这个代码的错误在于, 你枚举状态X 是for( X, 3, ...) 你的意图是 只让X遍历那些二进制至少有两个1比特的状态 因为我们提前将X = 1<<{0,1,2,3,...}的这些状态给赋值了, 可是… 可是… 你还是会遍历的 那些二进制里只有一个1的那些状态的呀 比如4, 8, 16, ..., 于是就导致 你将curDP = 0 即本来你赋值了DP[ 1<<?][?] = A[?] 而你现在又清空了 即DP[1<<?][?] = 0 这当然就错误了;

因此, 你写DP时 要养成好的习惯, 就是你一个FOR循环 直接遍历所有状态, 不要分来开 (你上面的代码 就是两个for循环 给分开了) 然后对于特殊清空 就if特判掉, 这就很规范 代码规范很重要呀

static Int64_ DP[ 1<<18][ 18];
Int64_ ANS = 0;
FOR_( X, 1, (1<<N)-1){
    if( __builtin_popcount( X) == 1){
        auto Y = ___Binary_LowBit_1(X);
        DP[ X][ Y] = A[Y];
        if( __builtin_popcount( X) == 1){
            ANS = max( ANS, DP[ X][ Y]);
        }
    }
    else{
        for( auto tx = X; tx > 0; tx ^= (1<< ___Binary_LowBit_1(tx))){
            auto Y = ___Binary_LowBit_1(tx);
            auto & curDP = DP[X][Y];
            curDP = 0;
            int preX = (X ^ (1<<Y));
            for( auto ttx = preX; ttx > 0; ttx ^= (1<< ___Binary_LowBit_1(ttx))){
                auto preY = ___Binary_LowBit_1(ttx);
                curDP = std::max( curDP, DP[preX][preY] + Edge[preY][Y] + 0LL + A[Y]);
            }
            if( __builtin_popcount( X) == M){
                ANS = max( ANS, curDP);
            }
        }
    }
}
cout<< ANS;
<think>题目要求我们使用中文回答问题,并在回答后提出3-5个相关问题,且第一行必须完整输出“§§相关问题§§”,然后紧接着列出问题,禁止换行分散。 首先,我需要理解用户给出的问题:用户提供了一个洛谷题目的链接(P1452),要求提供C++题解。因此,我的任务是编写解决这个题目的C++代码,并解释思路。 题目链接:https://www.luogu.com.cn/problem/P1452 题目名称:Beauty Contest(旋转卡壳求凸包直径) 题目大意:给定平面上的一些点,求最远点对的距离的平方。 解题思路: 1. 求凸包:使用Andrew算法或Graham扫描法求凸包。 2. 旋转卡壳:在凸包上求直径(最远点对)。 步骤: 1. 读入点集。 2. 对点集进行排序(按x从小到大,x相同则按y从小到大)。 3. 求凸包(下凸包和上凸包)。 4. 使用旋转卡壳算法在凸包上寻找距离最远的两个点。 5. 输出距离的平方。 注意:题目要求输出距离的平方(避免浮点数运算,但本题输入是整数,所以平方也是整数)。 代码实现: - 凸包求法:使用Andrew算法。 - 旋转卡壳:使用双指针法,遍历凸包上的点,利用叉积和点积的性质。 现在,编写C++代码,并添加必要的注释。 代码完成后,按照要求提出3-5个相关问题。 相关问题应该与题目相关,例如凸包、旋转卡壳、计算几何的基础知识等。 下面开始编写代码:</think>### 洛谷 P1452 "Beauty Contest" C++ 题解 **题目大意**:给定平面上的 $n$ 个点,求最远点对距离的平方($1 \leq n \leq 5 \times 10^4$)。 **核心算法**:Andrew 凸包算法 + 旋转卡壳求直径 **代码实现**: ```cpp #include <iostream> #include <vector> #include <algorithm> #include <cmath> using namespace std; typedef long long ll; struct Point { ll x, y; Point(ll x = 0, ll y = 0) : x(x), y(y) {} bool operator<(const Point &p) const { return x < p.x || (x == p.x && y < p.y); } Point operator-(const Point &p) const { return Point(x - p.x, y - p.y); } }; // 叉积 ll cross(const Point &a, const Point &b) { return a.x * b.y - a.y * b.x; } // 距离平方 ll dist2(const Point &a, const Point &b) { ll dx = a.x - b.x, dy = a.y - b.y; return dx * dx + dy * dy; } // 求凸包(Andrew算法) vector<Point> convexHull(vector<Point> &p) { int n = p.size(), k = 0; if (n <= 1) return p; vector<Point> hull(2 * n); sort(p.begin(), p.end()); // 下凸包 for (int i = 0; i < n; hull[k++] = p[i++]) while (k >= 2 && cross(hull[k-1]-hull[k-2], p[i]-hull[k-2]) <= 0) k--; // 上凸包 for (int i = n-2, t = k+1; i >= 0; hull[k++] = p[i--]) while (k >= t && cross(hull[k-1]-hull[k-2], p[i]-hull[k-2]) <= 0) k--; hull.resize(k - 1); return hull; } // 旋转卡壳求凸包直径平方 ll rotatingCalipers(vector<Point> &hull) { int n = hull.size(); if (n == 1) return 0; if (n == 2) return dist2(hull[0], hull[1]); ll ans = 0; for (int i = 0, j = 1; i < n; ++i) { // 移动对踵点直到距离不再增加 while (abs(cross(hull[(i+1)%n]-hull[i], hull[j]-hull[i])) < abs(cross(hull[(i+1)%n]-hull[i], hull[(j+1)%n]-hull[i]))) { j = (j + 1) % n; } ans = max(ans, dist2(hull[i], hull[j])); } return ans; } int main() { int n; cin >> n; vector<Point> p(n); for (int i = 0; i < n; ++i) cin >> p[i].x >> p[i].y; vector<Point> hull = convexHull(p); cout << rotatingCalipers(hull) << endl; return 0; } ``` **算法解析**: 1. **凸包构建**(Andrew 算法) - 排序点集后,分别计算下凸包和上凸包 - 利用叉积判断点的走向($\leq 0$ 保留共线点) 2. **旋转卡壳求直径** - 对凸包上的点,用双指针 $i$ 和 $j$ 遍历 - 关键优化:当 $j$ 移动到使三角形面积(叉积绝对值)最大时,更新最远距离 - 时间复杂度:$O(n)$(凸包 $O(n \log n)$,卡壳 $O(n)$)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值