do_while_true

一言(ヒトコト)

「题解」Codeforces 1698F Equal Reversal

为什么有些并不显然的东西在题解里是显然的啊,第一种方案构造方式是参考 UOJ 群里八云蓝教的。


\(a\) 能变换成 \(b\) 的充要条件是:

  • \(a_1=b_1,a_n=b_n\)

  • \(\{\{a_i,a_{i+1}\}|1\leq i<n,i\in N\}=\{\{b_i,b_{i+1}\}|1\leq i<n,i\in N\}\),也就是相邻元素的无序数对构成的集合相同。

必要:每次操作不会改变 \(a_1,a_n\),也不会改变相邻元素的无序数对的集合。

充分:考虑直接构造出这个方案。


第一种构造:

若现在已经满足 \(a_{[1,i]}\)\(b_{[1,i]}\) 相同,设 \(a_i=b_i=x,a_{i+1}=b\)\(b_{i+1}=c\)

那么 \(a_{[i+1,n]}\) 中必然也会出现 \(x,c\) 相邻(满足条件二)。

如果 \(a_{[i,n]}\)\([x,b,\cdots,c,x,\cdots]\),那么选择两个 \(x\) 作为端点旋转即可将 \(i+1\) 这个位置匹配成功;

如果 \(a_{[i,n]}\)\([x,b,\cdots x,c,\cdots]\),现在想办法把 \(x,c\) 转过来:

考虑 \(a\)\([x,b,\cdots,x,c]\) 与其对应(区间端点下标相同)在 \(b\) 中的 \([x,c,\cdots]\),若这两个区间不满足相邻元素的无序对构成的集合相同(称作平衡),考虑使得两段不平衡的一个数 \(y\),形式化地,若将 \(a\) 此段中相邻元素组成的无序数对构成的集合称作 \(A\)\(b\) 构成的称作 \(B\),则一定存在一个 \(y\) 满足 \(\{\{y,z\}|\{y,z\}\in A\}\neq \{\{y,z\}|\{y,z\}\in B\}\),由于 \(a\)\(b\) 的前 \(i\) 个已经相同,那么在 \(A\) 中没出现的 \(\{y,z\}\) 一定在选出的这一段的后面,所以 \(a_{[i,n]}\) 一定形如 \([x,b,\cdots,y,\cdots,x,c,\cdots,y,\cdots]\),选择两个 \(y\) 作为端点旋转即可将 \(i+1\) 这个位置匹配成功。注意到若 \(y=c\),那么在边界上可能会出现点问题,不过由于 \(|A|=|B|\),说明至少有两个这样的 \(y\),选择一个不为 \(c\) 的即可。

如果平衡,考虑 \(c\) 在无序对集合中出现的奇偶性,那么 \(b\) 中这一段的末尾一定是 \(c\),这表明 \(a\) 中出现了至少两次 \(c\),所以在这一段中 \(a\)\([x,b,\cdots,c,\cdots,x,c]\) 的形式,那么选择这两个 \(c\) 作为端点旋转即可。


第二种构造:

考虑建立一个图论模型,建立一个图 \(G\),对所有值建一个点,对于所有的 \(1\leq i<n\),将 \((a_i,a_{i+1})\) 连一条无向边。那么 \(a\) 给定了这张图的一条欧拉路径,而操作相当于翻转在 \(G\) 中遍历一个环的顺序。而结论是,通过若干次这样的操作,一定能得到其它任意的起点终点相同的欧拉路径。

首先操作是可逆的,所以考虑将任意两条不一样的路径变为一样的路径。

如果这两条路径从 \(x\) 点开始出发的边不同,一条路径 \(A\) 走了 \((x,y)\) ,另一条路径 \(B\) 走了 \((x,z)\)。根据欧拉路径的性质,在走这一步不一样的边之前,\(x\) 还剩下偶数条边没有经过,假设这个数目是 \(2k\),那么一条合法的欧拉路径会将剩下这 \(2k\) 条边定向为 \(k\) 条出边和 \(k\) 条入边。如果在 \(A\)\((x,z)\) 是入边,那么 \(A\) 翻转 \(x\to y\to \cdots \to z\to x\) 这个环即可使得 \(A\)\(B\) 在这一步上走相同的边;如果 \(B\)\((x,y)\) 是入边也同理。

那么现在还没考虑的情况就是 \(2k\) 条边中,\(A\)\(B\) 都将 \((x,y),(x,z)\) 定为了出边,还需要在剩下的 \((2k-2)\) 条边中定 \(k\) 条入边和 \((k-2)\) 条出边,如果 \(A\)\(B\) 有相同的入边 \((c,x)\),那么 \(A\) 翻转 \(x\to y\to \cdots \to c\to x\)\(B\) 翻转 \(x\to z\to \cdots \to c\to x\) 即可使得 \(A\)\(B\) 在这一步上走相同的边。而根据鸽巢原理,\((2k-2)\) 条边中 \(A\)\(k\) 条,\(B\) 也选 \(k\) 条,必定会有重复选的。所以一定能找到这样的 \(c\)

基于此,我们可以在每一步进行适当的翻转 \(A\)\(B\) 中一个环的遍历顺序,使得 \(A\)\(B\) 成为相同的欧拉路径。由于操作可逆,所以得到原结论,通过这样的若干次这样的操作,一定能得到其它任意的起点终点相同的欧拉路径。

#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<random>
#define pb emplace_back
#define mp std::make_pair
#define fi first
#define se second
#define dbg(x) cerr<<"In Line "<< __LINE__<<" the "<<#x<<" = "<<x<<'\n';
#define dpi(x,y) cerr<<"In Line "<<__LINE__<<" the "<<#x<<" = "<<x<<" ; "<<"the "<<#y<<" = "<<y<<'\n';
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>pii;
typedef pair<ll,int>pli;
typedef pair<ll,ll>pll;
typedef vector<int>vi;
typedef vector<ll>vll;
typedef vector<pii>vpii;
template<typename T>T cmax(T &x, T y){return x=x>y?x:y;}
template<typename T>T cmin(T &x, T y){return x=x<y?x:y;}
template<typename T>
T &read(T &r){
	r=0;bool w=0;char ch=getchar();
	while(ch<'0'||ch>'9')w=ch=='-'?1:0,ch=getchar();
	while(ch>='0'&&ch<='9')r=r*10+(ch^48),ch=getchar();
	return r=w?-r:r;
}
template<typename T1,typename... T2>
void read(T1 &x, T2& ...y){ read(x); read(y...); }
const int N=510;
int n,a[N],b[N];
vpii ans;
void Rev(int l,int r){
	ans.pb(mp(l,r));
	reverse(a+l,a+r+1);
}
bool Solve(int i){
	for(int j=i;j<n;j++)
		if(a[j]==b[i]&&a[j+1]==b[i-1]){
			Rev(i-1,j+1);
			return 1;
		}
	for(int p=i;p<n;p++)
		if(a[p]==b[i-1]&&a[p+1]==b[i]){
			bool fl=0;
			for(int j=i-1;j<=p&&!fl;j++)
				for(int k=p+1;k<=n;k++)
					if(a[j]==a[k]){
						Rev(j,k);
						fl=1;
						break;
					}
			if(!fl){
				return 0;
			}
			for(int j=i;j<n;j++)
				if(a[j]==b[i]&&a[j+1]==b[i-1]){
					Rev(i-1,j+1);
					return 1;
				}
		}
	return 0;
}
void solve(){
	read(n);ans.clear();
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=n;i++)read(b[i]);
	if(a[1]!=b[1]||a[n]!=b[n]){
		puts("NO");
		return ;
	}
	for(int i=1;i<=n;i++){
		if(a[i]==b[i])continue;
		if(!Solve(i)){
			puts("NO");
			return ;
		}
	}
	puts("YES");
	cout << ans.size() << '\n';
	for(auto x:ans)cout << x.fi << ' ' << x.se << '\n';
}
signed main(){
	int T;read(T);while(T--)solve();
    #ifdef do_while_true
		cerr<<'\n'<<"Time:"<<clock()<<" ms"<<'\n';
	#endif
	return 0;
}
posted @ 2022-06-30 10:10  do_while_true  阅读(74)  评论(0编辑  收藏  举报