Looking for Order (状压DP)

本文介绍了一个经典的路径规划问题,即如何在限制条件下找到将所有物品放回指定位置所需的最短路径。通过状态压缩动态规划的方法解决了该问题,并给出了详细的算法实现过程。

Looking for Order

Girl Lena likes it when everything is in order, and looks for order everywhere. Once she was getting ready for the University and noticed that the room was in a mess — all the objects from her handbag were thrown about the room. Of course, she wanted to put them back into her handbag. The problem is that the girl cannot carry more than two objects at a time, and cannot move the handbag. Also, if he has taken an object, she cannot put it anywhere except her handbag — her inherent sense of order does not let her do so.

You are given the coordinates of the handbag and the coordinates of the objects in some Сartesian coordinate system. It is known that the girl covers the distance between any two objects in the time equal to the squared length of the segment between the points of the objects. It is also known that initially the coordinates of the girl and the handbag are the same. You are asked to find such an order of actions, that the girl can put all the objects back into her handbag in a minimum time period.

Input

The first line of the input file contains the handbag's coordinates xs, ys. The second line contains number n (1 ≤ n ≤ 24) — the amount of objects the girl has. The following n lines contain the objects' coordinates. All the coordinates do not exceed 100 in absolute value. All the given positions are different. All the numbers are integer.

Output

In the first line output the only number — the minimum time the girl needs to put the objects into her handbag.

In the second line output the possible optimum way for Lena. Each object in the input is described by its index number (from 1 to n), the handbag's point is described by number 0. The path should start and end in the handbag's point. If there are several optimal paths, print any of them. 

Examples
Input

0 0
2
1 1
-1 1

Output

8
0 1 2 0 

Input

1 1
3
4 3
3 4
0 0

Output

32
0 1 2 0 3 0 
题意:

给n个物品的坐标, 和一个包裹的位置, 包裹不能移动。 每次最多可以拿两个物品, 然后将它们放到包里, 求将所有物品放到包里所需走的最小路程。

分析:

首先我们会发现,如果我们选两个点i,jni,j拿的话,起点为n那么先拿i还是先拿j是没有影响的

即他们距离总是dis[n][i]+dis[i][j]+dis[j][n]dis[n][i]+dis[i][j]+dis[j][n]

而且如果要选择这两个点的话,即每两个点若先事先分好哪两个点一起拿的话,那么点对的选取先后顺序同样不会影响结果

因此想到使用状压dp枚举状态,而这个状态就是只代表选与不选,与顺序无关

首先朴素的思想是枚举每种二进制状态

然后[0,n1][0,n−1]一层循环枚举看哪个点ii没选,就选上,然后里面在一层循环枚举选取第二个没被选取的点j,注意当i=ji=j时,相当于选取的一个点,然后进行状态转移

当时刚才我们提到了选取点顺序无关,而我们如果像上面那样循环的话,必然会出现i,j和j,i这样就出现了重复,没有必要,因此枚举第二个点的循环从第一个点的位置往后再枚举就行了

但是这样选取点的复杂度仍然是O(n2)O(n2)再加上状态枚举总的复杂度是O(2n×n2)O(2n×n2)

实际上在枚举了一种状态下,我们只需要选取第一个没选的点进行状态转移了,仍然是我们上面分析的,如果已经分好了点对,即哪两个点一起选,那么点对的选取先后顺序也不会对结果产生影响,而且,枚举后面的状态的时候一定会选取到,也就是我们固定了点对的按第一个点编号从小到大选取顺序,这样只需要枚举到第一个没选取的点即可

复杂度降为O(2n×n)O(2n×n)

最后一定要注意按位运算符和==的优先级,==优先级大于&,所以一定注意加括号

code:

#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int dp[1<<24],dis[25][25],pres[1<<24],path[550],cnt,n;
pair<int,int> a[25];
//dp[i]代表i状态下的最小值
//dis[i][j]表示i点到j点的距离
//pres[i]表示i状态的前一个状态是pres[i]状态
//path记录路径
int main(){
    int x,y;
    scanf("%d%d",&x,&y);
    scanf("%d",&n);
    a[n] = make_pair(x,y);
    for(int i = 0; i < n; i++){
        scanf("%d%d",&x,&y);
        a[i] = make_pair(x,y);
    }
    //预处理出每个点间的距离
    for(int i = 0; i <= n; i++){
        for(int j = 0; j <= n; j++){
            dis[i][j] = (a[i].first - a[j].first) * (a[i].first - a[j].first) + (a[i].second - a[j].second) * (a[i].second - a[j].second);
        }
    }
    memset(dp,inf,sizeof(dp));
    dp[0] = 0;
    for(int i = 0; i < (1 << n); i++){
        if(dp[i] == inf) continue;
        for(int j = 0; j < n; j++){
            if(((1 << j) & i) == 0){//如果j点没有拿,就拿
                for(int k = j; k < n; k++){//从j开始往后再拿一个,避免了重复
                    if(((1 << k) & i) == 0){
                        int tmp = i | (1 << j) | (1 << k);
                        int d = dis[j][k] + dis[n][j] + dis[n][k];
                        //特殊情况:当k=j相当于选取了一个点,此时就增加了j这个点,新增加的距离就是n->j,j->n即2*dis[n][j]
                        //其他情况:当k!=j相当于选取了两个点,此时新增加的距离为三段dis[j][k] + dis[n][j] + dis[n][k]
                        if(dp[tmp] > dp[i] + d){
                            dp[tmp] = dp[i] + d;//有点像最短路松弛操作
                            pres[tmp] = i;//记录下新状态的前一个状态
                        }
                    }
                }
                break;//对于每个枚举的状态我们只需要进行一次转移降低复杂度
                //即该状态下找到第一个没选取的点进行转移,后面点的选取就先不用枚举了
                //因为顺序不会影响结果,所以在枚举后面的状态的时候肯定还会枚举到
            }
        }
    }
    printf("%d\n",dp[(1<<n)-1]);
    for(int i = (1<<n)-1; i != 0; i = pres[i]){
        int tmp = pres[i] ^ i;//得到当前状态比前一个状态多的点
        path[cnt++] = 0;//每次先把起点放入
        for(int j = 0; j < n; j++){//枚举看看是哪个点
            if((1 << j) & tmp){
                path[cnt++] = j + 1;
            }
        }
    }
    path[cnt++] = 0;
    //因为是逆着存的所以逆着输出就是正向路径
    for(int i = cnt-1; i >= 0; i--){
        printf("%d ",path[i]);
    }
    return 0;
}
DP超详细教程:从入门到精通 DP缩动态规划)是一种非常实用的算法技巧,特别适合处理态可以用二进制表示的问题。下面我将用最详细、最系统的方式讲解这个技术,保证你能彻底理解。 一、DP的本质 1.1 什么是缩? 缩的核心思想是:用二进制位来表示某种态。比如: 有5个灯泡:可以用5位二进制数表示它们的开关态 10101表示第1、3、5个灯亮,2、4灭 有8个任务是否完成:可以用8位二进制数表示 11001001表示第1、2、5、8个任务已完成 1.2 为什么需要态? 传统DP在表示某些态时会遇到困难。例如: 棋盘放置问题:要记录哪些格子被占用 任务分配问题:要记录哪些任务已被分配 路径问题:要记录哪些点已经访问过 如果用传统数组表示,可能需要多维数组,空间复杂度爆炸。而用二进制缩,一个整数就能表示复杂的态。 二、DP的三大组成部分 2.1 态表示 用一个整数的二进制形式表示态: 每一位代表一个元素的态(选中/未选中,存在/不存在等) 整数范围:0到2ⁿ-1(n是元素个数) 示例:3个物品的选择态 000(0):都没选 001(1):选第1个 010(2):选第2个 011(3):选第1、2个 ... 111(7):全选 2.2 态转移 定义如何从一个态转移到另一个态,通常包括: 检查当前态的某些位 根据条件修改某些位 生成新态 2.3 DP数组设计 dp[state]或dp[state][i],其中: state是缩后的态 i可能是附加信息(如当前位置、已选数量等) 三、必须精通的位运算技巧 3.1 基本操作 操作 代码表示 示例(假设8位二进制) 设置第i位为1 `state (1 << i)` `0010 (1<<2) → 0110` 设置第i位为0 state & ~(1 << i) 0110 & ~(1<<2) → 0010 切换第i位 state ^ (1 << i) 0110 ^ (1<<2) → 0010 检查第i位是否为1 (state >> i) & 1 (0110 >> 2) & 1 → 1 3.2 高级技巧 枚举所有子集: cpp for(int subset = state; subset; subset = (subset-1)&state){ // 处理subset } 最低位的1: cpp int lowbit = x & -x; 统计1的个数: cpp int count = __builtin_popcount(state); // GCC内置函数 六、DP的优化技巧 6.1 预处理合法态 很多问题中,大部分态是不合法的,可以预先筛选: cpp vector<int> valid_states; for (int state = 0; state < (1 << n); ++state) { if (check(state)) { // 检查state是否合法 valid_states.push_back(state); } } 6.2 滚动数组优化 当态只依赖前一个阶段时,可以节省空间: cpp vector<vector<int>> dp(2, vector<int>(size)); // 只保留当前和上一个态 int now = 0, prev = 1; for (int i = 1; i <= n; ++i) { swap(now, prev); for (auto& state : valid_states) { dp[now][state] = 0; // 清空当前态 // 态转移... } } 6.3 记忆化搜索实现 有时递归形式更直观: cpp int memo[1<<20][20]; // 记忆化数组 int dfs(int state, int u) { if (memo[state][u] != -1) return memo[state][u]; // 递归处理... return memo[state][u] = res; } 七、常见问题与调试技巧 7.1 常见错误 位运算优先级:总是加括号,如(state & (1 << i)) 数组越界:态数是2ⁿ,不是n 初始态设置错误:比如TSP中dp[1][0] = 0 边界条件处理不当:如全选态是(1<<n)-1,不是1<<n 7.2 调试建议 打印中间态:将二进制态转换为可视化的形式 cpp void printState(int state, int n) { for (int i = n-1; i >= 0; --i) cout << ((state >> i) & 1); cout << endl; } 从小规模测试用例开始(如n=3,4) 使用assert检查关键假设 八、学习路线建议 初级阶段: 练习基本位操作 解决简单问题(如LeetCode 464、526题) 中级阶段: 掌握经典模型(TSP、棋盘覆盖) 学习优化技巧(预处理、滚动数组) 高级阶段: 处理高维(如需要同时缩多个态) 结合其他算法(如BFS、双指针) 九、实战练习题目推荐 入门题: LeetCode 78. Subsets(理解态表示) LeetCode 464. Can I Win(简单DP) 中等题: LeetCode 526. Beautiful Arrangement LeetCode 691. Stickers to Spell Word 经典题: POJ 2411. Mondriaan's Dream(棋盘覆盖) HDU 3001. Travelling(三进制) 挑战题: Codeforces 8C. Looking for Order Topcoder SRM 556 Div1 1000. LeftRightDigitsGame2 记住,掌握DP的关键在于: 彻底理解二进制态表示 熟练运用位运算 通过大量练习培养直觉 希望这份超详细的教程能帮助你彻底掌握DP!如果还有任何不明白的地方,可以针对具体问题继续深入探讨。 请帮我转成markdown语法输出,谢谢
08-13
【最优潮流】直流最优潮流(OPF)课设(Matlab代码实现)内容概要:本文档主要围绕“直流最优潮流(OPF)课设”的Matlab代码实现展开,属于电力系统优化领域的教学与科研实践内容。文档介绍了通过Matlab进行电力系统最优潮流计算的基本原理与编程实现方法,重点聚焦于直流最优潮流模型的构建与求解过程,适用于课程设计或科研入门实践。文中提及使用YALMIP等优化工具包进行建模,并提供了相关资源下载链接,便于读者复现与学习。此外,文档还列举了大量与电力系统、智能优化算法、机器学习、路径规划等相关的Matlab仿真案例,体现出其服务于科研仿真辅导的综合性平台性质。; 适合人群:电气工程、自动化、电力系统及相关专业的本科生、研究生,以及从事电力系统优化、智能算法应用研究的科研人员。; 使用场景及目标:①掌握直流最优潮流的基本原理与Matlab实现方法;②完成课程设计或科研项目中的电力系统优化任务;③借助提供的丰富案例资源,拓展在智能优化、态估计、微电网调度等方向的研究思路与技术手段。; 阅读建议:建议读者结合文档中提供的网盘资源,下载完整代码与工具包,边学习理论边动手实践。重点关注YALMIP工具的使用方法,并通过复现文中提到的多个案例,加深对电力系统优化问题建模与求解的理解。
<think> 我们面对的用户问题是关于1 package寻找资金的。根据问题,用户提到"1 package is looking for funding",这通常出现在Node.js的npm包管理中,当安装某些包时,可能会遇到这个提示,意思是有一个包正在寻求资金支持。用户可能是在安装包时看到这个提示,想知道如何解决或理解这个提示。 因此,我们需要搜索关于npm包寻找资金(funding)的相关信息,包括这个提示出现的原因、如何操作(比如如何禁止这个提示,或者如何支持包作者)等。 搜索关键词建议: "npm package looking for funding" "how to suppress npm funding message" "npm funding" 根据搜索,我们可能会得到以下信息: 1. 这个提示是npm的一个特性,目的是让用户知道维护者正在寻求资金支持,以便用户可以选择支持包作者。 2. 用户可以通过运行`npm fund`命令查看项目的依赖中哪些包在寻求资金支持,并获取如何资助的信息。 3. 如果用户不想看到这个提示,可以在安装时使用`--no-fund`选项,或者在npm配置中设置`fund=false`。 我们将整合这些信息,生成回答。 注意:回答中要避免第一人称,避免步骤词汇,将答案组织为多个方法或步骤(每个步骤间换行),并且最后按照要求添加相关问题部分。 由于问题中没有涉及代码和公式,我们不需要使用公式格式,但如果有命令,我们会用代码块展示。 回答结构: 1. 解释提示的含义。 2. 提供查看寻求资金支持的包的方法。 3. 提供禁止提示的方法(两种:命令行选项和配置设置)。 最后,生成相关问题。 开始生成回答: </think>### npm包寻求资金支持的说明与处理方法 在Node.js环境中执行`npm install`时出现“1 package is looking for funding”提示,表明安装的依赖包中有1个包正在寻求资金支持。这是npm引入的资助提醒机制,旨在帮助开源维护者获得可持续的资金支持。以下是具体说明和操作指南: --- #### **1. 提示产生的原因** - npm从v6.13.0版本开始支持`funding`字段,包作者可在`package.json`中声明资助链接(如Open Collective、Patreon等)。 - 当安装的依赖包包含此字段时,npm会输出提示,例如: ``` 1 package is looking for funding run `npm fund` for details ``` --- #### **2. 查看寻求资助的包** 执行以下命令查看具体包名和资助链接: ```bash npm fund ``` 输出示例: ``` my-package@2.0.0 └── URL: https://example.com/fund ``` --- #### **3. 禁用资助提示的方法** **方法一:单次安装时禁用** 在安装命令中添加`--no-fund`参数: ```bash npm install --no-fund ``` **方法二:永久禁用提示** 修改npm全局配置: ```bash npm config set fund false --global ``` 此配置会关闭所有后续安装的资助提示。 --- #### **4. 支持开源维护者** 若希望资助包作者,可通过以下方式: - 运行`npm fund <package-name>`获取该包专属资助链接 - 直接访问包项目的GitHub仓库或官网查找捐赠入口 - 通过[Open Collective](https://opencollective.com/)等平台搜索项目 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值