题目

描述



题目大意

给你两个排列AAA和BBB,每次随即选三个数进行轮换操作,问mmm次操作内使AAA变成BBB的概率。


思考历程

首先随便搞一下,就变成了AAA中每个数回归自己原位。

一眼望去,感觉nnn很小……

最简单的想法是将每个情况都储存起来,然后搞出它们之间的转移情况。

然后发现这些状态是存不下的。

于是我就开始想有没有哪些状态是等价的。

然后我发现对于每个数字,可以简单地归为是否回归原位的两种情况。这样状态倒小了,可是又能怎么转移呢?mmm这么大,肯定打矩阵乘法。这么大的状态还是不能过啊!

于是我就放弃了希望:还能怎么压?


正解

题解的方法真是令人惊叹。

不过题解说得晦涩难懂,我还是用人话来解决一下。

我们可以把当前的数组看成一个边集,表示从某个点连向另一个点。

显然点数有nnn个,边数nnn个,并且每个点有且仅有一个出度(和入度)。

那么这个图就是由几个环组成的。

如果我们将同环中的三个数拿出来轮换,轮换过后它们依然能够在这个环中,环的大小不变。

我们可以感性地理解它为等价的。

那么我们换一下状态的表示方法。对于每个状态,我们记录每个环的大小,用个桶来存它。

环的内部结构具体是什么可以不用去理睬它,我们只知道这些都是一样的,这就足够了(感性大法好)。

显然,每个数回归到自己原位就相当于是nnn个环,这种情况只会有一种,我们最终要算出来的是这个的答案,所以不会被其它杂七杂八的东西给影响(假设这个状态的编号为111)

让我们算一算这样压缩状态的状态数,然后就可以发现这是nnn的划分数,当n=14n=14n=14时只有135135135。

这么小的数据,当然可以矩阵乘法了。

于是我们就开始设fi,jf_{i,j}fi,j​为状态iii转移到状态jjj的概率。

有个问题是如何转移。

一开始我想了很久,但最后才发现我想复杂了。实际上有个最简单也最粗暴的方法:造出一个排列!

随便造出一个满足这个状态的排列,然后O(n3)O(n^3)O(n3)地转移,将转移过后的排列变成状态。这样就可以记录了。

当然,要用个mapmapmap或打哈希表来记录每种状态的编号。

由于nnn很小,这样跑还特别快。

最后是要注意的地方,题目说在mmm次操作内复原,所以到了111状态后,就不需要再转移出来了。

也就是f1,1=1f_{1,1}=1f1,1​=1且对于i>1i> 1i>1,使得f1,i=0f_{1,i}=0f1,i​=0

时间复杂度就不用分析了吧……


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#define mo 998244353
map<long long,int> bz;
int n,invn3,m;
int a[15],b[15],c[15];
long long my_hash(int s[]){
long long res=0;
for (int i=n;i>=1;--i)
res=res*(n+1)+s[i];
return res;
}
int s[15];
void fors(int k,int sum,int i,void work()){//枚举状态……套了个函数
if (sum==0){
work();
return;
}
for (;i<=sum;++i){
s[i]++;
fors(k+1,sum-i,i,work);
s[i]--;
}
}
int cnt;
void init(){
bz[my_hash(s)]=++cnt;
}
int *tran(int *a){
static bool vis[15];
static int s[15];
memset(vis,0,sizeof vis);
memset(s,0,sizeof s);
for (int i=0;i<n;++i){
if (vis[i])
continue;
int c=0;
for (int j=i;!vis[j];j=a[j],c++)
vis[j]=1;
s[c]++;
}
return s;
}
struct Matrix{
int mat[151][151];
inline void operator*=(Matrix &b){
static Matrix res;
for (int i=1;i<=cnt;++i)
for (int j=1;j<=cnt;++j){
long long sum=0;
for (int k=1;k<=cnt;++k)
sum+=1ll*mat[i][k]*b.mat[k][j]%mo;
res.mat[i][j]=sum%mo;
}
memcpy(mat,res.mat,sizeof res);
}
inline void get_pow(int n){
static Matrix res;
memset(res.mat,0,sizeof res);
for (int i=1;i<=cnt;++i)
res.mat[i][i]=1;
for (;n;n>>=1,*this*=*this)
if (n&1)
res*=*this;
memcpy(this,res.mat,sizeof res);
}
} f;
void calc(){
static int tmp[15],tmp2[15];
int numt=bz[my_hash(s)];
if (numt==1){
f.mat[1][1]=1;
return;
}
for (int i=1,j=0;i<=n;++i)//造一个序列
for (int k=0;k<s[i];++k){
int jj=j;
for (;j<jj+i;++j)
tmp[j]=j-1;
tmp[jj]=j-1;
}
memcpy(tmp2,tmp,sizeof tmp);
for (int i=0;i<n;++i)
for (int j=0;j<n;++j)
if (i!=j)
for (int k=0;k<n;++k)
if (i!=k && j!=k){
tmp2[i]=tmp[k],tmp2[j]=tmp[i],tmp2[k]=tmp[j];
int numt2=bz[my_hash(tran(tmp2))];
(f.mat[numt][numt2]+=invn3)%=mo;
tmp2[i]=tmp[i],tmp2[j]=tmp[j],tmp2[k]=tmp[k];
}
}
int main(){
freopen("goodbye.in","r",stdin);
freopen("goodbye.out","w",stdout);
scanf("%d%d",&n,&m);
invn3=1;
for (int i=mo-2,tmp=1ll*n*(n-1)%mo*(n-2)%mo;i;i>>=1,tmp=1ll*tmp*tmp%mo)
if (i&1)
invn3=1ll*invn3*tmp%mo; //invn3=(n*(n-1)*(n-2))^(-1)
for (int i=0;i<n;++i){
scanf("%d",&a[i]);
a[i]--;
}
for (int i=0;i<n;++i){
scanf("%d",&b[i]);
b[i]--;
c[b[i]]=a[i];
} fors(0,n,1,init);
fors(0,n,1,calc);
f.get_pow(m);
int numa=bz[my_hash(tran(c))];
printf("%d\n",f.mat[numa][1]);
return 0;
}

用的语法可能有点骚……不过应该能看懂吧?


总结

这道题的压缩手段,不可不谓是极致了。

[JZOJ4913] 【GDOI2017模拟12.3】告别的更多相关文章

  1. jzoj4918. 【GDOI2017模拟12.9】最近公共祖先 (树链剖分+线段树)

    题面 题解 首先,点变黑的过程是不可逆的,黑化了就再也洗不白了 其次,对于\(v\)的祖先\(rt\),\(rt\)能用来更新答案当且仅当\(sz_{rt}>sz_{x}\),其中\(sz\)表 ...

  2. jzoj4915. 【GDOI2017模拟12.9】最长不下降子序列 (数列)

    题面 题解 调了好几个小时啊--话说我考试的时候脑子里到底在想啥-- 首先,这个数列肯定是有循环节的,而且循环节的长度\(T\)不会超过\(D\) 那么就可以把数列分成三份,\(L+S+R\),其中\ ...

  3. jzoj4916. 【GDOI2017模拟12.9】完全背包问题 (背包+最短路)

    题面 题解 考场上蠢了--这么简单的东西都想不到-- 首先排序加去重. 先来考虑一下,形如 \[a_1x_1+a_2x_2+...a_nx_n=w,a_1<a_2<...<a_n,x ...

  4. 【GDOI2017模拟12.9】最近公共祖先

    题目 分析 首先,将这些节点按dfs序建一棵线段树. 因为按dfs序,所以在同一子树上的节点会放在线段树相邻的位置. 发现,对于一个位置x,它的权值只会对以x为根的子树造成影响. 当修改x时,用w[x ...

  5. 【JZOJ4925】【GDOI2017模拟12.18】稻草人

    题目描述 YLOI村有一片荒地,上面竖着N个稻草人,村民们每年多次在稻草人们的周围举行祭典. 有一次,YLOI村的村长听到了稻草人们的启示,计划在荒地中开垦一片田地.和启示中的一样,田地需要满足以下条 ...

  6. noip模拟12[简单的区间·简单的玄学·简单的填数]

    noip模拟12 solutions 这次考试靠的还是比较好的,但是还是有不好的地方, 为啥嘞??因为我觉得我排列组合好像白学了诶,文化课都忘记了 正难则反!!!!!!!! 害没关系啦,一共拿到了\( ...

  7. Noip模拟12 2021.7.12

    T1 interval 亏得昨天晚上改掉了T3并且理解了单调栈,今天一扫这题目就知道要用啥了. 先预处理出以a[i]为最大值的最大左右区间.然后再将a[i]取%!!!是的,要不然会影响单调栈的使用.. ...

  8. NOIP模拟12

    也算是最近几次比较水的一次吧. 考试时看T1像个打表找规律的题,扔了,去看T2,带修莫队??不会,完戏.看了T3,我决定还是去看T1. 看着T1,我突然发现T2是个大水题:主席树就行,不带修,修改时只 ...

  9. [考试总结]noip模拟12

    菜 今天总体来说 菜爆了,打了 \(3\) 个暴力,没有一个是正解,并且每一个分数都低得要命... 主要还是太菜了... 第一题开题发现和昨天 \(T3\) 一样,然而因为还没学可持久化数据结构就咕掉 ...

随机推荐

  1. 修改linux默认语言

    linux修改默认语言   编辑/etc/sysconfig/i18n这个文件(不存在就新建一个),原内容如下: LANG="en_US.UTF-8" SYSFONT=" ...

  2. (c# )操作Excel的时候出现:不能使用对象或链接

    可能就是你打开了多个Excel进程没有关掉出现的问题. 编程:对文件操作的时候要养成关掉进程的习惯 在c#中操作完后关闭资源的代入如下: System.Runtime.InteropServices. ...

  3. log4j架构

    Log4j API设计为分层结构,其中每一层提供了不同的对象,对象执行不同的任务.这使得设计灵活,根据将来需要来扩展. 有两种类型可用在Log4j的框架对象. 核心对象: 框架的强制对象和框架的使用. ...

  4. 你没玩过的全新版本!Win10这些骚操作你知多少

    你没玩过的全新版本!Win10这些骚操作你知多少 [PConline技巧]不知不觉,Win10与我们相伴已经整整四个年头了,从最开始的组团抗拒到现在的默默接受,个中滋味相信谁心里都有个数.近日微软开始 ...

  5. CSS3教程:Responsive框架常见的Media Queries片段

    CSS3 Media Queries片段在这里主要分成三类:移动端.PC端以及一些常见的响应式框架的Media Queries片段.移动端Media Queries片段iPhone5@media sc ...

  6. spark2.+ sql 性能调优

    1.在内存中缓存数据 性能调优主要是将数据放入内存中操作,spark缓存注册表的方法 版本 缓存 释放缓存 spark2.+ spark.catalog.cacheTable("tableN ...

  7. oracle 管理表空间

    表空间:是oracle数据库中最大的逻辑存储结构,与操作系统中的数据文件相对应,用于存储数据库中用户创建的所有内容 表空间>数据文件 4.1创建一个永久性表空间myspace create ta ...

  8. JSON Web Token (JWT)生成Token及解密实战。

    昨天讲解了JWT的介绍.应用场景.优点及注意事项等,今天来个JWT具体的使用实践吧. 从JWT官网支持的类库来看,jjwt是Java支持的算法中最全的,推荐使用,网址如下. https://githu ...

  9. Django开发学习BUG记录--RemovedInDjango19Warning:Model class apps.user.models.User doesn't declare an explicit app_label

    报错信息: /home/python/PycharmProjects/dailyfresh/apps/user/models.py:8: RemovedInDjango19Warning: Model ...

  10. C#学习笔记----反射基础

    反射基础 反射用于在程序运行过程中,获取类里面的信息或发现程序集并运行的一个过程.通过反射可以获得.dll和.exe后缀的程序集里面的信息.使用反射可以看到一个程序集内部的类,接口,字段,属性,方法, ...