其他分享
首页 > 其他分享> > CF1672F1(构造,置换)

CF1672F1(构造,置换)

作者:互联网

Description

给定一个序列 \(a\),定义一次操作为交换序列中的两个位置上的元素,求 \(a\) 的一个排列 \(b\),满足将 \(b\) 还原成 \(a\) 所需最少操作数最多。

\(1\leq n\leq 2\times 10^5\),\(a_i\leq n\)。


Solution

定义 \(swap(x,y)\) 为交换 \(x\) 和 \(y\) 两个数。

首先根据我们小学二年级学的置换的知识,无论怎么交换 \(b\) 的元素最后都等价于对 \(b\) 的一次置换,我们设其为 \(p\)。

置换是由若干个轮换复合成的,对于一个大小为 \(m\) 的轮换,不难得出想要实现它则需要 \(m-1\) 次操作。那么我们对 \(p\) 进行分解,设分出了 \(k\) 个轮换,那么实现这个置换则需要 \(n-k\) 次操作。

假设 \(a\) 是 \([1,n]\) 的一个排列,那么问题就转化为求一个置换 \(p^{-1}\),使得 \(p^{-1}\) 的分解尽量小,于是直接将一个轮换作为 \(p^{-1}\) 即可。(\(b\) 通过置换 \(p\) 得到 \(a\),则 \(a\) 通过置换 \(p^{-1}\) 得到 \(b\);逆置换和原置换结构是一样的,分解也一样。)


如果 \(a\) 可重,那么就不行了,因为通过 \(p^{-1}\) 置换出的 \(b\) 想还原成 \(a\) 所用的置换就不一定为 \(p\) 了。

假设出现次数最多的数 \(x\) 出现了 \(k\) 次,那么我们可以得到答案的上界即为 \(n-k\),因为无论 \(b\) 长什么样,我们都可以强制将除了 \(x\) 以外的数用 \(n-k\) 次操作归位,剩下的 \(k\) 个 \(x\) 也就回到了自己的位置。

于是我们可以就猜测这个上界是紧的,想办法去构造达到这个上界。


随即就有了一个很 naive 的想法:那么我们对于每个 \(1\leq i\leq k\),处理一批位置 \(\{j|j\text{ 为 }a_j \text{ 第 }i \text{ 次出现的位置}\}\)。这样就满足了对于每个 \(i\) 的位置集合所对应的 \(a\) 的值互不相同,那么我们对于每个 \(i\) 的位置集合随便搞个轮换,整个 \(p^{-1}\) 就由 \(k\) 个轮换复合成,答案似乎就能取到 \(n-k\)。

然后 WA on pretest 2 了。

hack:

1
7
1 2 4 3 1 3 2

假设我们随便取轮换,取到了:

3 1 2 4 | 2 1 3

我们发现这样只要 \(4\) 次操作就能还原,小于 \(n-k=5\)。


Lemma1:

\(p^{-1}\) 分解为 \(k\) 个轮换,并且不存在一对位置 \((i,j)\) 满足 \(i\) 和 \(j\) 所在轮换的大小大于 \(2\) 且 \(a_i=b_j,a_j=b_i\) (即等价于在两个轮换中如果令 \(p_{g1}=i\),\(p_{g2}=j\),那么有 \(a_{g1}=a_j\),\(a_{g2}=a_i\)),是答案取到上界的充要条件。

proof:

充分性显然,必要性证一下。

说人话就是将这两个位置交换 就正好还原了这两个位置,那么我们考虑这两个位置交换后的影响,设 \(i\) 所在的轮换为 \(t_i\),\(j\) 所在的轮换为 \(t_j\),当前未还原位置个数为 \(n\),对应的轮换有 \(k\) 个,则剩余还需要 \(ans=n-k\) 次操作。

删掉 \(i\) 和 \(j\) 两个位置,\(n'=n-2\),然后考虑 \(k\) 的变化,假设能将残缺的 \(t'_i\) 或 \(t'_j\) (为方便我们假设为 \(t'_i\))和另一个轮换 \(t_k\) 合并,那么一定满足 \(t'_i\) 和 \(t_k\) 有重复元素。若 \(t_i\) 变为 \(t'_i\) 删掉的数不是 \(x\),由我们构造轮换的方法可知 \(t'_i\) 和 \(t_k\) 都含有 \(x\),矛盾;假设删掉了 \(x\),由我们构造轮换的方法可知任意两个 \(t\) 一定存在包含关系,若 \(t_i\subset t_k\) 显然不满足,\(t_k\subset t_i\) 的话,想要满足 \(t_k\) 一定只要一个数 \(x\),与假设矛盾。那么可得删完后 \(k'=k\) ,新答案为 \(ans'=n'-k'=ans-2\),用一次操作减小了 \(2\) 的代价,则答案一定取不到上界。


我们就只要在上面的构造的基础上满足这个限制一个,似乎想去用数据结构维护,但发现也找不到合适的,于是继续贪。

假设我们当前要处理的位置集合 \(s_i\),我们将 \(j\in s_i\) 按 \(a_j\) 出现次数大小降序排序,那么 \(s_i\) 一定是 \(s_{i-1}\) 的一个前缀(可能相等)。

那么我们直接按 \(s\) 的顺序构造整体左移一个位置的轮换即可。因为对于当前不是 \(s_i\) 的最后一个数的 \(j\),\(p_g=j\) 的 \(g\) 是固定的,不会出现上面的情况;若 \(j\) 是最后一个,那么 \(p_g=j\) 的 \(g\) 变成了 \(s_i\) 的第一个元素,除非 \(s_i\) 的大小为 \(2\),也不会出现上面那种情况,但大小为 \(2\) 的轮换对上面的 lemma 没有影响。

于是构造完了。


Code

代码是真的好写,证明也是真的难证。

#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,tune=native")
#include<bits/stdc++.h>
using namespace std;
#define rg register
#define ll long long
#define ull unsigned ll
#define lowbit(x) (x&(-x))
#define djq 998244353
const double eps=1e-8;
const int inf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f;
const double alpha=0.73;
inline void file(){
	freopen("1.in","r",stdin);
	freopen("1.out","w",stdout);
}
char buf[1<<21],*p1=buf,*p2=buf;
inline int getc(){
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,(1<<20)+5,stdin),p1==p2)?EOF:*p1++;
}
//#define getc getchar
inline int read(){
	rg int ret=0,f=0;char ch=getc();
    while(!isdigit(ch)){if(ch==EOF)exit(0);if(ch=='-')f=1;ch=getc();}
    while(isdigit(ch)){ret=ret*10+ch-48;ch=getc();}
    return f?-ret:ret;
}
#define epb emplace_back
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define it iterator
#define mkp make_pair
typedef vector<int> vec;
typedef pair<int,int> pii;
struct point{ int x,y; point(int x=0,int y=0):x(x),y(y) {} inline bool operator<(const point& T)const{ return x^T.x?x<T.x:y<T.y; }; };
inline int ksm(int base,int p){int ret=1;while(p){if(p&1)ret=1ll*ret*base%djq;base=1ll*base*base%djq,p>>=1;}return ret;}
int n,a[200005],b[200005];
int cnt[200005],mx;
vec x[200005];
signed mian(){
	n=read(); mx=0; 
	for(rg int i=1;i<=n;++i) a[i]=read(),cnt[i]=0,x[i].clear();
	for(rg int i=1;i<=n;++i) ++cnt[a[i]],mx=max(mx,cnt[a[i]]),x[cnt[a[i]]].epb(i);
	for(rg int i=1;i<=mx;++i){
		const int m=x[i].size();
		sort(all(x[i]),[&](const int& x,const int& y){return (cnt[a[x]]^cnt[a[y]])?cnt[a[x]]>cnt[a[y]]:a[x]<a[y]; });
		for(rg int j=0;j<m;++j) b[x[i][j]]=a[x[i][(j+1)%m]];
	}
	for(rg int i=1;i<=n;++i) printf("%d ",b[i]);
	return puts(""),0;
}
signed main(){
	int _=read();
	while(_--) mian();
	return 0;
}
/*
*/ 

标签:那么,int,置换,位置,构造,我们,CF1672F1,轮换
来源: https://www.cnblogs.com/tiatto/p/16188280.html