zoukankan      html  css  js  c++  java
  • 【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;
    } 
    
  • 相关阅读:
    学点 C 语言(21): 数据类型 数组与指针
    学点 C 语言(16): 数据类型 关于常量的前缀、后缀
    学点 C 语言(19): 数据类型 数组
    学点 C 语言(15): 数据类型 sizeof(检测类型大小)
    学点 C 语言(18): 数据类型 枚举类型(enum)
    学点 C 语言(20): 数据类型 指针
    重写一个字符串分割函数 回复 "tomzw" 的问题
    学点 C 语言(14): 数据类型 双字节字符类型 wchar_t
    学点 C 语言(22): 数据类型 多维数组与指针
    学点 C 语言(17): 数据类型 因类型引发的问题或错误
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/luogu_P7796.html
Copyright © 2011-2022 走看看