【luogu P7796】图书管理员 / POLICE(并查集)(树状数组)

图书管理员 / POLICE

题目链接:luogu P7796

题目大意

给你 n 个书架,每个书架有 m 个位置,然后有一些书在上面。
给出图书的初始放置状态和期望放置状态。
然后你有两个操作:
如果一本书左边或右边是空的,那你可以把这本书移到左边或右边。
从书架上拿走一本书,放在一个空位上。
然后问你整理到期望放置状态最少要用多少次拿书的操作,如果无法达到期望状态也要判断出来。

思路

首先考虑无解的情况,不难想到当且仅当没有空格子而且初始状态跟期望的不一样。

然后先看看部分分,不难想到把数重新按第一个的顺序重新编号,然后其实就是对第二个跑一个 LIS。
得出的 LIS 的值就可以通过第一个操作得到,剩下的就要移动了。然后不难想到如果这一列有空的,那就要移多少个就是多少,那如果没有空的,就要把一个移出去,移完剩下的再移回来,所以要比移的个数加一。

那你考虑这个移动会飞到另一个书架。
那如果完全解决的,就不用管了。
那你可以考虑建一个图,从 i 书架飞到 j 书架就 i 连向 j

那你会发现你可以相当于建无向图的话,一个连通块之间才会相互关联。
那如果这连通块间没有空的,那每个点入度等于初度,就是一个欧拉回路,那你总要有一个拿出去,才可以把图跑掉,然后就可以放回去,所以这个是要加一的。
那如果有空的,那我们像一个书架里面的一样,类比一下(前面也可以类比),就是不用加。(具体就是你可以加几条虚的使它变成欧拉回路,那因为有虚的,我们从虚的出发开始跑欧拉回路,虚的就代表不操作,就不需要加一了)
不难看出这里不用真的建无向图,只需要用个并查集就可以了。

然后就好了。
具体的可以看看代码。

代码

#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; struct node { int x, num, xx; }c[1001]; int n, m, a[1001][1001], b[1001][1001]; int dy[1000001], tree[1001], ans, f[1001]; int lis[1001], pp[1001], tot[1001], fa[1001]; bool kong; bool cmp1(node x, node y) { return x.x < y.x; } bool cmp2(node x, node y) { return x.num < y.num; } int query(int x) { int re = 0; for (; x; x -= x & (-x)) re = max(re, tree[x]); return re; } void insert(int x, int y, int n) { for (; x <= n; x += x & (-x)) tree[x] = max(tree[x], y); } int work(int x) {//求 LIS(进而求出一个书架单独的答案) int nn = 0, tmp = 0, re = 0; for (int i = 1; i <= m; i++) if (a[x][i]) dy[a[x][i]] = ++nn; for (int i = 1; i <= m; i++) if (b[x][i] && dy[b[x][i]]) c[++tmp].x = dy[b[x][i]], c[tmp].num = tmp; sort(c + 1, c + tmp + 1, cmp1);//离散化一下方便树状数组(重新编号) c[1].xx = 1; for (int i = 2; i <= tmp; i++) if (c[i].x == c[i - 1].x) c[i].xx = c[i - 1].xx; else c[i].xx = c[i - 1].xx + 1; sort(c + 1, c + tmp + 1, cmp2); memset(tree, 0, sizeof(tree)); for (int i = 1; i <= tmp; i++) { f[i] = query(c[i].xx - 1) + 1; re = max(re, f[i]); insert(c[i].xx, f[i], nn); } lis[x] = re; pp[x] = nn; tot[x] = m; for (int i = 1; i <= m; i++) if (a[x][i]) dy[a[x][i]] = 0; return nn - re; } int find(int now) { if (fa[now] == now) return now; return fa[now] = find(fa[now]); } void connect(int x, int y) { int X = find(x), Y = find(y); if (X == Y) return ; lis[Y] += lis[X]; pp[Y] += pp[X]; tot[Y] += tot[X]; fa[X] = Y; } int main() { // freopen("librarian.in", "r", stdin); // freopen("librarian.out", "w", stdout); scanf("%d %d", &n, &m); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) { scanf("%d", &a[i][j]); if (!a[i][j]) kong = 1; } for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) { scanf("%d", &b[i][j]); if (!kong && a[i][j] != b[i][j]) {//没有 printf("-1"); return 0; } } if (!kong) {//已经一模一样 printf("0"); return 0; } for (int i = 1; i <= n; i++) { ans += work(i); } for (int i = 1; i <= n; i++) fa[i] = i; memset(dy, 0, sizeof(dy)); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (b[i][j]) dy[b[i][j]] = i;//连边 for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (a[i][j]) connect(i, dy[a[i][j]]); for (int i = 1; i <= n; i++) if (find(i) == i && lis[i] != pp[i]) ans += (pp[i] == tot[i]);//如果这个块有空的就不用多挪一次,否则就要 printf("%d", ans); fclose(stdin); fclose(stdout); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P7796.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(1317)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示