题目描述
从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序),求矩阵中每一对相邻元素之差的绝对值之和。
提供几种思路吧,顺着最常用的思路先写一份DFS减枝,分别枚举选择的行和列,卡常严重所以反反复复减枝超级复杂。 因为不是题目正解所以不多加以注释解释了。
#include <bits/stdc++.h>
using namespace std;
int a[20][20];
int yclb[20][20][20], yclc[20][20][20], ycld[20][20];
int ycla[20], cnt = 2e9; // 前缀和数组
int n, m, r, c;
void dfsr(int x, int e, int ans, int last);
void dfsl(int x, int e, int last)
{
if (x == r)
{
dfsr(0, 0, 0, 0);
return;
}
for (int i = e + 1; i <= x + n - r + 1; i++)
{
int lj = 0;
if (x)
{
for (int j = 1; j <= m; j++)
{
ycla[j] += yclb[j][last][i];
}
}
for (int j = 1; j <= m; j++)
for (int k = j + 1; k <= m; k++)
{
ycld[j][k] += yclc[i][j][k];
}
dfsl(x + 1, i, i);
if (x)
{
for (int j = 1; j <= m; j++)
{
ycla[j] -= yclb[j][last][i];
}
}
for (int j = 1; j <= m; j++)
for (int k = j + 1; k <= m; k++)
{
ycld[j][k] -= yclc[i][j][k];
}
}
}
void dfsr(int x, int e, int ans, int last)
// x当前选了几列 y=c e用于减枝某列 ans记录上一次的答案 last记录上一列
{
if (x == c)
{
cnt = min(ans, cnt);
return;
}
if (ans > cnt)
return;
for (int i = e + 1; i <= m; i++)
{
int ans2 = ans + ycla[i] + ycld[last][i];
if (ans2 < cnt)
dfsr(x + 1, i, ans2, i);
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> r >> c;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
}
//预处理下面的数组
for (int i = 1; i <= m; i++) // 列
for (int j = 1; j <= n; j++) // 行
for (int k = j + 1; k <= n; k++) // 行
{
yclb[i][j][k] = abs(a[j][i] - a[k][i]);
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
for (int k = j; k <= m; k++)
{
yclc[i][j][k] = abs(a[i][j] - a[i][k]);
}
dfsl(0, 0, 0);
cout << cnt;
return 0;
}
。。调得我头昏,,,卡常严重,改用DP试试。 因为这题涉及两个位置状态量,如果我们用二维的做法进行DP比较困难。 那我们还是沿用上方的部分思路,可以枚举行,将一个二维的问题转化为一个一维DP,优化时间复杂度。
步骤:
先枚举行(DFS或上面的二进制枚举),让所选择的行变为已知量。
计算每列中上下行之间差的绝对值之和 ①
计算每行中任意列与列之间差的绝对值之和 ②
则我们的相邻元素的值为 ①+②
知道以上条件后之后,我们的问题就变为: 如何在m列中任选c列,使其相邻元素的值最小?
以下是dp部分代码:
dp[0][0] = 0;
for (int i = 1; i <= c; i++)
{
for (int j = i; j <= m; j++)
{
for (int k = 0; k < j; k++)
{
dp[i][j] = min(dp[i][j], dp[i - 1][k] + rows[j] + column[k][j]);
}
if (i == c)
ans = min(ans, dp[i][j]);
}
}
如果上面还不会做的话提供完整代码(我这里用的二进制枚举+dp)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll a[20][20], dp[20][20], rows[20], column[20][20];
//分别是 a:输入矩阵的数组 dp:用于进行动态转移
//rows:存储每一列所有枚举的相邻行的数值
//column:表示从j列到k列的 相邻数之的差(累加所有枚举的行)
int n, m, r, c;
ll ans = 0x3f3f3f3f;//答案
vector<int> line;//存放当前选择的行号
void DP(int row)
{
memset(dp, 127, sizeof(dp)); // 清零
memset(rows, 0, sizeof(rows)); // 清零
memset(column, 0, sizeof(column)); // 清零
for (int i = 0; i < n; i++)
{
if (row & (1 << i))
line.push_back(i + 1);
}
for (int i = 1; i <= m; i++) // 列
for (int j = 1; j < line.size(); j++)
rows[i] += abs(a[line[j - 1]][i] - a[line[j]][i]); // 计算每列 行与行之间的值的和 设为 A
for (int i = 0; i < line.size(); i++) // 行
{
for (int j = 1; j <= m; j++) // 列
for (int k = j + 1; k <= m; k++) // 列
{
column[j][k] += abs(a[line[i]][j] - a[line[i]][k]); // 计算每行 列到列的累加 设为 B
}
}
dp[0][0] = 0; // dp 数组状态 初始化 必须为0
for (int i = 1; i <= c; i++) // 当前选取的状态
{
for (int j = i; j <= m; j++) // 当前枚举的列
{
for (int k = 0; k < j; k++) // 上一次选取的列
{ // 动态转移方程
dp[i][j] = min(dp[i][j], dp[i - 1][k] + rows[j] + column[k][j]);
}
if (i == c)
ans = min(ans, dp[i][j]);
}
}
line.clear(); // 如果用vector 一定要记得复原
}
int main()
{
cin >> n >> m >> r >> c;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
}
for (int i = 1; i < (1 << n); i++)
//先二进制枚举行 将二维问题转换为一维问题再动态规划
{
if (__builtin_popcount(i) != r)//满足当前选择的数
continue;
DP(i);
}
cout << ans;
return 0;
}