题意
给出一个矩阵与目标矩阵。
有两种操作:
1、移动数字到相邻(左右)空位置,耗费0。
2、拿出数字放到空位置,耗费1。
求出最小耗费完成转换。
对于\(50\%\)的数据,保证每本书在初始和最终状态下都会在同一个书架上。
对于所有数据,\(1\leqslant n,m\leqslant1000\)。
思路
对于\(50\%\),考虑每行怎么计算。
将每本书它的目标位置存到数组中。
对于这个数组求\(LIS\),那么答案即为\(sz_i-lis_i\),其中\(sz_i\)表示起始与目标在相同书架上的书的个数。
因为移到空位置不需耗费,对于一段前后顺序在操作前与操作后不变的书,可以通过空位置来调整。其余都要抽出。这个最长一段即\(LIS\)。
对于\(50\%\),考虑有书本要移到其他书架上的情况。
如果有书要从书架\(i \to j\),将其连边。
形成若干连通块。
对于一个连通块,若里面无空书架,要用其他的书架来进行中转,且目标状态也无空书架。
为了空出一个空书架,这个连通块里的操作次数加上\(1\)。
可以发现这个连通块形成一个欧拉回路,在空出一个位置后,其他书架按照欧拉回路来移动,都会出现空出一个位置的情况。
对于一个有空书架的书架里答案就为上面所说的\(sz_i-lis_i\)。
对于一个连通块,若里面一开始存在空书架,不用加上\(1\)即可。
连边用并查集来操作。
贴一个更详细分类讨论的博客。
代码
#pragma GCC optimize(2)
#pragma GCC optimize("Ofast")
#include <queue>
#include <cstdio>
#include <cstring>
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf;
std::queue<int> q;
int n, m, ans;
int a[1001][1001], emp[1001], sz[1001], lis[1001], fa[1001];
std::pair<int, int> b[1000001];
int tree[1001];
void add(int p, int val) {
for (; p <= m; p += p & -p)
tree[p] = std::max(val, tree[p]);
}
int ask(int p) {
int res = 0;
for (; p; p -= p & -p)
res = std::max(res, tree[p]);
return res;
}
int find(int x) {
return fa[x] = fa[x] == x ? x : find(fa[x]);
}
void con(int x, int y) {
int f1 = find(x), f2 = find(y);
if (f1 == f2)
return;
fa[f2] = f1;
emp[f1] |= emp[f2];
sz[f1] += sz[f2];
lis[f1] += lis[f2];
}
inline int read() {
int res = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
res = res * 10 + (c ^ 48), c = getchar();
return res * f;
}
int main() {
freopen("librarian.in", "r", stdin);
freopen("librarian.out", "w", stdout);
n = read(), m = read();
int dif = 0, empt = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
a[i][j] = read();
if (!a[i][j])
emp[i] = 1, empt = 1;
else
sz[i]++;
}
for (int i = 1, tmp; i <= n; i++)
for (int j = 1; j <= m; j++) {
tmp = read();
if (tmp)
b[tmp] = std::make_pair(i, j);
if (a[i][j] != tmp)
dif = 1;
}
if (!dif)
return !printf("0");
if (dif && !empt)
return !printf("-1");
for (int i = 1; i <= n; i++) {
memset(tree, 0, sizeof(tree));
for (int j = 1; j <= m; j++)
if (b[a[i][j]].first == i)
q.push(b[a[i][j]].second);
while (q.size()) {
int x = q.front(), tmp = ask(x);
q.pop();
lis[i] = std::max(lis[i], tmp + 1);
add(x, tmp + 1);
}
}
for (int i = 1; i <= n; i++)
fa[i] = i;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i][j] && b[a[i][j]].first != i)
con(i, b[a[i][j]].first);
for (int i = 1; i <= n; i++)
if (find(i) == i && sz[i] != lis[i])
ans += sz[i] - lis[i] + (1 - emp[i]);
printf("%d", ans);
}