Atcoder 600 Sorted and Sorted 逆序数,DP

题意:给出n个黑球和n个白球的编号和其位置.(a[i],i).
操作:交换相邻两个球的位置.
1<=n<=2e3. 问将2n个球排成有序,(任意两个黑球(i,j),i<j 使得球i在球j之前. 白球也是如此).问最少操作次数?


设(x,y)为逆序对:当x原先在y左边,全部操作后x在y的右边.
那么每操作一次,都产生一个逆序对.若知道最后的序列 则其操作次数为总的逆序数对.


设dp[i][j] 将前i个白球和前j个黑球排序的最小代价.w[i][j]为白球i与白球[1:i-1]及黑球[1,j]产生的逆序对.

dp[i][j]=min(dp[i-1][j]+w[i][j],dp[i][j-1]+b[i][j]).

#include <bits/stdc++.h>
using namespace std;
const int N=2e3+5;
int n,w[N],b[N],x;//w[x] 编号x的位置. 
int dp[N][N],cw[N][N],cb[N][N];
int main()
{
	memset(cw,0,sizeof(cw));
	memset(cb,0,sizeof(cb));
	scanf("%d",&n);
	for(int i=1;i<=2*n;i++)
	{
		char op[2];
		scanf("%s%d",op,&x);
		if(op[0]=='W')
			w[x]=i;
		else
			b[x]=i;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++)
			if(w[i]<w[j])
				cw[i][0]++;
		for(int j=1;j<=n;j++)
		{
			cw[i][j]=cw[i][j-1];
			if(w[i]<b[j])
				cw[i][j]++;	
		}
	}	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++)	
			if(b[i]<b[j])
				cb[0][i]++;
		for(int j=1;j<=n;j++)
		{
			cb[j][i]=cb[j-1][i];
			if(b[i]<w[j])
				cb[j][i]++;
		}
	}
	int c1=0,c2=0;
	for(int i=1;i<=n;i++)
	{
		c1+=cw[i][0],c2+=cb[0][i];
		dp[i][0]=c1,dp[0][i]=c2;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			dp[i][j]=min(dp[i-1][j]+cw[i][j],dp[i][j-1]+cb[i][j]);	
	cout<<dp[n][n]<<'\n';
	return 0;
}

<think>嗯,用户想了解背包问题求具体方案的方法。首先,我需要回顾一下背包问题的基本概念。背包问题通常是动态规划的经典例子,分为0-1背包和完全背包等类型。用户提到的是求具体方案,所以需要确定在动态规划之后如何回溯得到选择的物品。 首先,我应该解释0-1背包问题的标准动态规划解法。状态定义通常是dp[i][j],表示前i个物品在容量j下的最大价值。然后状态转移方程是考虑选或不选第i个物品。这部分用户可能已经知道,但需要简要回顾以确保基础。 接下来,关键是如何根据dp数组回溯找到具体方案。常规的做法是从后往前检查每个物品是否被选中。例如,如果dp[i][j]不等于dp[i-1][j],说明第i个物品被选中,然后减去它的重量继续检查。但可能需要考虑逆序还是正序遍历物品,这里可能需要用二维数组记录状态,或者使用一维数组时需要注意更新顺序。 用户可能想知道具体的实现步骤,所以需要分步骤说明。例如,首先填充dp表,然后从结果倒推,逐个判断物品是否被选中。这时候需要逆序检查物品,从最后一个开始,比较当前容量下的价值是否与不选该物品时的价值加上该物品的价值相等。 另外,可能存在多种最优解的情况,这时候需要说明如何处理,比如按字典序最小的方案输出,或者输出所有可能方案。这时候可能需要调整物品遍历的顺序,比如在回溯时优先选择编号较小的物品。 还要考虑空间优化的问题。如果使用一维数组来优化空间,回溯可能会更复杂,因为丢失了部分状态信息。这时候可能需要使用二维数组来保存完整的状态,方便回溯。 用户可能在实际编码中遇到问题,比如如何记录选择路径。可能需要使用额外的数组来记录每个状态的选择情况,或者直接在回溯时通过比较dp值来判断。需要解释这两种方法的优缺点,比如空间换时间,或者时间换空间。 另外,示例是很有帮助的。举一个具体的例子,比如物品的重量和价值,以及背包容量,然后逐步展示如何填充dp表和回溯过程。这可以让用户更直观地理解步骤。 还要注意边界条件,比如容量为0或者没有物品时的处理,以及物品索引是否正确。这些细节在实现时容易出错,需要提醒用户检查。 最后,总结一下关键点:动态规划求解最大价值,逆向回溯确定具体物品,处理多种解的情况,以及可能的实现技巧和注意事项。确保用户不仅了解算法步骤,还能应用到实际编码中。</think>### 背包问题求具体方案的分步解析 #### 1. 问题定义 **0-1背包问题**:给定 $n$ 个物品(体积 $v_i$,价值 $w_i$)和容量为 $V$ 的背包,求一种物品选择方案,使得总价值最大且总体积不超过 $V$。要求输出具体选择的物品编号。 --- #### 2. 动态规划求解最大价值 **状态定义**: 定义二维数组 $dp[i][j]$,表示前 $i$ 个物品中选,总体积不超过 $j$ 时的最大价值。 **状态转移方程**: $$ dp[i][j] = \max\left( dp[i-1][j], \ dp[i-1][j-v_i] + w_i \right) $$ **初始化**: $$ dp[0][j] = 0 \quad (0 \leq j \leq V) $$ --- #### 3. 回溯求解具体方案 **核心思路**: 从最终状态 $dp[n][V]$ 逆向推导,判断每个物品是否被选中。 **具体步骤**: 1. **初始化**:当前剩余容量 $j = V$。 2. **逆序遍历物品**:从第 $n$ 个物品倒推到第 1 个。 3. **判断是否选中**: - 若 $dp[i][j] = dp[i-1][j - v_i] + w_i$,则说明选中了第 $i$ 个物品。 - 更新剩余容量 $j = j - v_i$。 4. **记录选中的物品编号**。 --- #### 4. 实现细节 **字典序最小方案**: 若要求字典序最小的方案,需**正序遍历物品**,但在回溯时从后向前判断。此时需调整状态转移方向。 **示例代码框架(Python)**: ```python n, V = 4, 5 v = [2, 3, 4, 5] # 体积 w = [3, 4, 5, 6] # 价值 # 初始化dpdp = [[0]*(V+1) for _ in range(n+1)] for i in range(1, n+1): for j in range(V+1): dp[i][j] = dp[i-1][j] if j >= v[i-1]: dp[i][j] = max(dp[i][j], dp[i-1][j - v[i-1]] + w[i-1]) # 回溯找方案 res = [] j = V for i in range(n, 0, -1): if j >= v[i-1] and dp[i][j] == dp[i-1][j - v[i-1]] + w[i-1]: res.append(i) j -= v[i-1] print("选中的物品编号(逆序):", sorted(res)) ``` --- #### 5. 关键点总结 - **逆向推导**:必须从最终状态倒推,才能确定每个物品是否被选中。 - **时间复杂度**:$O(nV)$,空间复杂度 $O(nV)$(可优化为一维数组,但需调整遍历顺序)。 - **多解处理**:若存在多个最优解,此方法默认输出最后遍历到的方案。若需字典序最小,需在状态转移时优先选择编号小的物品。 --- #### 6. 扩展:一维数组优化 若用一维数组 $dp[j]$,需倒序更新容量: ```python dp = [0]*(V+1) for i in range(1, n+1): for j in range(V, v[i-1]-1, -1): dp[j] = max(dp[j], dp[j - v[i-1]] + w[i-1]) ``` 回溯时需额外记录选择路径,建议保留二维数组以便回溯。 --- 通过以上步骤,可准确求解0-1背包问题的具体方案。实际编码时需注意物品编号与数组索引的对应关系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值