2020牛客暑期多校训练营Just Shuffle(置换群,逆元)

Just Shuffle

题目描述

在这里插入图片描述

输入描述:

2020牛客暑期多校训练营Just Shuffle(置换群,逆元)_第1张图片

输出描述:

在这里插入图片描述

示例1

输入

3 998244353
2 3 1

输出

3 1 2

题目大意

给定1~n的大小为n的数列以及置换的次数,再有一个结果数列,求一次置换后的数列是什么

分析1

题意直观解释,以样例为例。
2020牛客暑期多校训练营Just Shuffle(置换群,逆元)_第2张图片
看官们可以先看下这篇论文里面有第二种思路,这里就不先赘述了。接下来先看官方的题解方式。

首先看到这题,肯定是先手动去模拟下。
2020牛客暑期多校训练营Just Shuffle(置换群,逆元)_第3张图片
多试几组数据,可以发现,多次转置后,可能会出现分成不同的团,这些团的内部不断的循环转置,易得,当团转置其长度的倍数次时,会变回原数列,因此k可以直接mod掉(毕竟1e9不是开玩笑的)。我们可以设每团原数列为T,转置的次数即为T的指数,因此,我们知道了T ^k,要求T ^1。看到1,就想到了乘法逆元。
可以设k关于数列长度p的逆元为inv,
则k *inv=1,。
所以T ^1=(T ^k) ^inv=T ^k *inv。
于是重点即为求k的逆元,首先考虑费马小定理,但是用它求的话p必须是素数,然而p是团的长度,是不一定为素数的。因此只能用EXGCD求解同余方程求逆元,EXGCD的要求是k和p互质,而k是比n大的素数,必定与n互质,也一定与p互质。
随后,求出了k的逆元inv,问题就是怎么把T ^k再转置inv次。
其实很简单,只要用vector存入团中的点,然后一一对应过去到答案数组即可。具体见代码。

代码

#include
#define ll long long
#define inf 1<<30
using namespace std;
const int MAXN=1e5+100;
vector<ll> vec;
//ll ksm(ll a,ll m){
//	ll ret=1;
//	while(m){
//		if(m&1) ret=ret*a%vec.size();
//		a=a*a%vec.size();
//		m>>=1;
//	}return ret;
//}本来想用费马求的……
ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){x=1;y=0;return a;}
	ll c=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return c;
}//扩欧
ll t[MAXN],ans[MAXN];
bool v[MAXN];
int main()
{
	ll l,k;
	scanf("%lld%lld",&l,&k);
	for(int i=1;i<=l;i++) scanf("%lld",&t[i]);
	for(int i=1;i<=l;i++){
		if(v[i]) continue;v[i]=1;vec.clear();//找到一个未处理的团
		vec.push_back(i);
		for(int j=t[i];j!=i;j=t[j])	vec.push_back(j),v[j]=1;//把这个团怼到vector里
		if(vec.size()==1){ans[i]=t[i];continue;}//这是一开始加的,用费马有个-2,会出现负数
		//T了好几遍,后来才知道可以用扩欧
		ll inv,y;
		exgcd(k%vec.size(),vec.size(),inv,y);//求解逆元,也可以用暴力循环(毕竟数据水啊)
		inv=inv<0?inv+vec.size():inv;//若是负数,就要加mod
		for(int j=0;j<vec.size();j++)
			ans[vec[j]]=vec[(j+inv)%vec.size()];//再把答案从vector里怼到ans里
	}
	for(int i=1;i<=l;i++) printf("%lld ",ans[i]);
}//不开ll也是可以的,一开始为了排除ll导致WA的可能

分析2

开头的论文中已经给出了k=gcd(l,k)*k/gcd(l,k),由此可以得到a’[i]=a[k *i mod l]。于是可以用for直接怼出答案数组,具体可以看开头的那篇博客和这段标程。此处不再赘述。毕竟要用官方题解

END

写的草率,有错即评,欢迎挑刺。

你可能感兴趣的:(2020牛客多校)