P9001 [CEOI 2022] Parking 题解

P9001 [CEOI 2022] Parking 题解


前言

笔者认为自己的分析较有利于代码实现,而且理解也来的容易。

知识点

构造,模拟,图论,环,链。

分析

暴力

首先对于 \(M\le 4\) 的部分有暴力 BFS,可以 \(O(m^2(2m)!)\) 过。

性质

然后我们考虑找性质,对于一种颜色的车,有三种类型:

  1. 两辆车都是「底下的车」。
  2. 一辆车是「底下的车」,另一辆是「顶上的车」。
  3. 两辆车都是「底上的车」。

那么区分开这些类型之后,发现 \(2N\le M\) 的部分只要略加讨论就可以过:

  1. 两辆车都是「底下的车」:选择一个作为最后停留的位置。
  2. 一辆车是「底下的车」,另一辆是「顶上的车」:选择「底下的车」所在处作为最后停留的位置。
  3. 两辆车都是「底上的车」:找一个空位把两辆车都塞进去。

那么由于满足 \(2N\le M\),会有很多原本就空着的位置,那么即使很随意地选择也可以使上面的方案合法,我们用拓扑排序就可以过。

建图

接下来我们继续分析。考虑移动原图,我们将数字一样的放到相邻,那么样例就变成了:

\[\begin{matrix} 0 & 3 & 2 & 0 & 4\\ 1 & 1 & 3 & 2 & 4\\ \end{matrix} \]

这样变得非常容易观察,我们可以将其分为两个部分,其中 \(\begin{matrix} 4\\ 4\\ \end{matrix}\) 的部分较为简单,就不赘述(但是记得特判)。剩下的就是:

\[\begin{matrix} 0 & 3 & 2 & 0 \\ 1 & 1 & 3 & 2 \\ \end{matrix} \]

这似乎像一条链,将两个点堆在一起,于是我们想到了图论建模:将每个车位中的两辆车的颜色连一条边(如果有两辆)。

由于每个点度数 \(\le 2\),那么最后会形成一堆链和简单环,上面就是我们最简单的一种环。

处理

于是我们对各种链和环的情况讨论,会发现其实此时求解最小步数已经非常简单,剩下的就是如何让方案合法。其主要影响因素其实是类型 2(两辆车都是「底上的车」),因为有类型 2 就代表必须有空位给它用,所以我们可以以类型 2 作为依据来分类讨论:

  1. 链,没有类型 2:

    就是我们上面举的例子,这样的链一定有一个类型 0 和一堆类型 1,比如下面这个:

    \[\begin{matrix} 0 & a_1 & a_2 & a_3 & \ldots & a_{k} & a_{k+1} & \ldots & a_{n-1} & a_n & 0 \\ a_1 & a_2 & a_3 & \ldots & a_{k} & x & x & a_{k+1} & \ldots & a_{n-1} & a_n \\ \end{matrix} \]

    其中 \(x\) 左边或右边的 \(a\) 均可能没有。

    那么这种情况处理很简单,也不用占用额外的空位,反而在处理完之后会多出一个空位。

  2. 链,有类型 2:

    其实类型 2 可以看作起一个连接作用,将很多上面情况 1 的链连接起来就是我们这里的情况 2,例如下面中的 \(b_1\)

    \[\begin{matrix} 0 & a_{1,1} & \ldots & a_{1,n_1} & b_1 & b_1 & a_{2,1} & \ldots & a_{2,n_2} & 0 \\ a_{1,1}& \ldots & x_1 \ x_1 & \ldots & a_{1,n_1} & a_{2,1}& \ldots & x_2 \ x_2 & \ldots & a_{2,n_2} \\ \end{matrix} \]

    于是我们需要一个额外的空位来处理 \(b_1\),然后就变成了两个情况 1。

    那么如果是多个,那么也只需要一个额外空位,因为将能处理的情况 1 处理完之后会多出一个空位,弥补了我们刚刚用掉的那个。

    当然这个情况在处理完之后也会多出一个空位。

  3. 环,没有类型 2:

    这种清况比较简单,毕竟没有类型 2,但是要成环的话,每个车位上下两个必须都有一个,上下总数需要相同,那么就不能有类型 0。也就是说只能有类型 1,例如:

    \[\begin{matrix} a_1 & a_2 & a_3 & \ldots & a_{n-1} & a_n \\ a_2 & a_3 & \ldots & a_{n-1} & a_n & a_1 \\ \end{matrix} \]

    那么我们只需要一个额外的空位就可以解决。不过这里的处理细节容易出错。

  4. 环,有且只有一个类型 2:

    那么这种情况可以看做情况 1 的链用一个类型 2 首尾相接,用一个额外的空位将类型 2 处理了之后,剩下的就是情况 1 了。

    \[\begin{matrix} b & a_1 & a_2 & a_3 & \ldots & a_{k} & a_{k+1} & \ldots & a_{n-1} & a_n & b \\ a_1 & a_2 & a_3 & \ldots & a_{k} & x & x & a_{k+1} & \ldots & a_{n-1} & a_n \\ \end{matrix} \]

  5. 环,有多于一个类型 2:

    类似情况 4,去掉一个类型 2 之后就变成了情况 2,那么这个需要两个额外的空位。

    \[\begin{matrix} b_2 & a_{1,1} & \ldots & a_{1,n_1} & b_1 & b_1 & a_{2,1} & \ldots & a_{2,n_2} & b_2 \\ a_{1,1}& \ldots & x_1 \ x_1 & \ldots & a_{1,n_1} & a_{2,1}& \ldots & x_2 \ x_2 & \ldots & a_{2,n_2} \\ \end{matrix} \]

简单整理一下,可以得到:

类型 需要额外空位数 产生额外空位数
情况 1:链,没有类型 2 0 1
情况 2:链,有类型 2 1 1
情况 3:环,没有类型 2 1 0
情况 4:环,有且只有一个类型 2 1 0
情况 5:环,有多于一个类型 2 2 0

那么很明显,我们从情况 1 顺次处理到情况 5 就可以做到尽可能合法,剩下的代码实现也比较简单。

实现

输入和预处理

首先考虑处理出每种颜色的类型 \(ty\) 和位置 \(pos_{0/1}\)

建图

我们先找出链,考虑从每个链的一端(注意一端的度数也可以是 \(0\))开始深搜,然后按数量区分为情况 1 和情况 2。

为了方便,接着我们以类型 2 作为起点,搜出每个有类型 2 的环,按数量区分为情况 4 和情况 5。

最后剩下的就是情况 3 了,无需区分。

处理

我们将链中情况 1 用双指针处理,然后情况 2 将类型 2 处理了之后就是情况 1,那么链的部分处理完了。

对于情况 3 的链,我们直接循环扫一遍就可以解决。

而对于情况 4 和情况 5,我们把作为起点的类型 2 处理了就变成链的部分了。

检查

注意检查原本就匹配的,以及判断合法性!!!

代码

时空复杂度 \(O(n)\)

constexpr int N(2e5+10);

int n,m,ans;
int rt[N],ty[N],cnt[N];
int pos[N][2];
vector<int> Emp,Rt[5];
vector<int> g[N],vec[N];
vector<pair<int,int>> Ans;

void Move(int x,int y) { return Ans.emplace_back(x,y); }

void dfs(int u) {
	if(ty[u]==2)++cnt[rt[u]];
	vec[rt[u]].push_back(u);
	for(int v:g[u])if(!rt[v])rt[v]=rt[u],dfs(v);
}

void List(vector<int> &vec) {
	for(int l(0),r(0); l<(int)vec.size(); l=r+2) {
		r=l;
		while(r+1<(int)vec.size()&&ty[vec[r+1]]!=2)++r;
		if(r+1<(int)vec.size()) {
			const int v(vec[r+1]),emp(Emp.back());
			Emp.pop_back(),Move(pos[v][0],emp),Move(pos[v][1],emp);
		}
		int h(l),t(r);
		while(h<t) {
			const int &v(vec[ty[vec[h]]?h++:t--]);
			Move(pos[v][1],pos[v][0]);
		}
		const int &v(vec[h]);
		Move(pos[v][1],pos[v][0]),Emp.push_back(pos[v][1]);
	}
}

template<const bool ty>void Circle(vector<int> &vec) {
	if(vec.empty())return;
	if(!ty) {
		if(pos[vec.front()][0]!=pos[vec.back()][1])reverse(vec.begin(),vec.end());
		const int v(vec.back()),emp(Emp.back());
		Emp.pop_back(),Move(pos[v][1],emp),pos[v][1]=emp;
		for(int v:vec)Move(pos[v][1],pos[v][0]);
		Emp.push_back(emp);
		return;
	}
	const int v(vec.front()),emp(Emp.back());
	Emp.pop_back(),Move(pos[v][0],emp),Move(pos[v][1],emp);
	vec.erase(vec.begin()),List(vec);
}

signed main() {
	/*DE("Input & Init");*/
	I(n,m);
	FOR(i,1,m) {
		int u,v;
		I(u,v);
		if(u&&v&&u!=v)g[u].push_back(v),g[v].push_back(u);
		!u?Emp.push_back(i),0:pos[u][pos[u][0]>0]=i;
		if(v)++ty[v],pos[v][pos[v][1]<=0]=i;
	}
	/*DE("Build");*/
	// lists
	FOR(i,1,n)if(!rt[i]&&pos[i][0]!=pos[i][1]&&(int)g[i].size()<=1)
		rt[i]=i,dfs(i),Rt[cnt[i]>0].push_back(i);
	// circles
	FOR(i,1,n)if(!rt[i]&&pos[i][0]!=pos[i][1]&&ty[i]==2)
		rt[i]=i,dfs(i),Rt[(cnt[i]<=1?3:4)].push_back(i);
	FOR(i,1,n)if(!rt[i]&&pos[i][0]!=pos[i][1])
		rt[i]=i,dfs(i),Rt[2].push_back(i);
	/*DE("Solve");*/
	for(int u:Rt[0])List(vec[u]);
	if(Emp.empty()&&(!Rt[1].empty()||!Rt[2].empty()||!Rt[3].empty()||!Rt[4].empty()))
		return puts("-1"),0; // check point 1
	for(int u:Rt[1])List(vec[u]);
	if((int)Emp.size()<=1&&!Rt[4].empty())return puts("-1"),0; // check point 2
	for(int u:Rt[2])Circle<0>(vec[u]);
	for(int u:Rt[3])Circle<1>(vec[u]);
	for(int u:Rt[4])Circle<1>(vec[u]);
	/*DE("Output");*/
	O(Ans.size(),'\n');
	for(const pair<int,int> &p:Ans)O(p.first,' ',p.second,'\n');
	return 0;
}

posted @ 2025-09-13 19:52  Add_Catalyst  阅读(10)  评论(0)    收藏  举报