Technocup 2021 - Elimination Round 3 CD

Kirito_w發表於2020-12-23

C. Peaceful Rooks

題意是每行每列同時只能存在一個點,每次可以對一個點進行橫向移動或縱向移動,現在想要求將這些點都移動到主對角線上所需的最小次數。

思路:對於每個點來說,要麼可以直接移動到所在行(或列)的主對角線上,要麼可以先移動到其他空餘的地方,等其他的點都移動到主對角線上之後,再將這個點移動到主對角線上。

所以實際上我們對某個點可以進行這樣的判斷:

  1. 如果可以直接移動,那麼就不用進行多餘操作;
  2. 如果不能直接移動,那麼就開始找有沒有其他可以直接移動的點。

如果在找的過程中發現有的點可以直接移動到主對角線上,那麼在這個點移動之後,其他的點自然可以依次移動的對角線上。
而如果在找的過程中發現有環出現了,即沒有點可以直接移動了,那麼就需要先把初始的點騰位置,然後讓其他的點到主對角線上,最後在把初始點移動到對角線上,可以發現相比可以直接移動的情況,次數多了一次。

之後就是模擬過程,思路不難,但程式碼細節需要注意!

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f30f
#define endl '\n'

using namespace std;

const int N = 1e5 + 100;

int n, m, b[N], bj[N], h[N], gg[N], sum;

void dfs(int x, int s)
{
	if(bj[x])   //如果成環了
	{
		gg[h[x]] = 0;
		sum += s;  //這裡雖然是+s,但是實際上是加的 s + 1,因為是當進入了下一層dfs之後才判斷是否成環。
		return ;
	}
	if(h[x] == x)  //如果本來就不需要移動
		return ;
	if(h[x] != x)  //如果需要移動
	{
		if(gg[h[x]])  //判斷需要移動的這一列上是否有,有的話遞迴騰位置,找到所有在一個集合裡的點
		{
			bj[x] = 1;   //判環標記陣列
			dfs(h[x], s + 1);  //要移到與所在行相同的一列上
		}
		else    //沒有的話說明可以直接移動,不用騰位置
		{
			sum += s;   
			return ;
		}
		gg[x] = 0;   //free點所在的行,防止重複計算
	}
	return ;
}

void solve()
{
	memset(b, 0, sizeof b);
	memset(gg, 0, sizeof gg);
	memset(h, 0, sizeof h);
	sum = 0;
	cin >> n >> m;
	for(int i = 0; i < m; i ++)
	{
		int x, y;
		cin >> x >> y;
		gg[y] = 1; // 所在列標記上
		h[y] = x;  //壓縮存圖, 第y列x行是點i
		b[i] = y;  //存點
	}
	for(int i = 0; i < m; i ++)
		if(gg[b[i]])
		{
			memset(bj, 0, sizeof bj);
			dfs(b[i], 1); // 深搜
		}
	cout << sum << endl;
}

int main()
{
	int _;
	cin >> _;
	while(_ --)
		solve();
	return 0;
}

相關文章