ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CF1672F1(构造,置换)

2022-04-24 23:33:34  阅读:150  来源: 互联网

标签:那么 int 置换 位置 构造 我们 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有