【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;
} 
posted @ 2021-08-24 09:16  あおいSakura  阅读(1313)  评论(0编辑  收藏  举报