题目描述
给两个长度为
n
n
n 的排列,从第一个排列开始,每次操作随机确定一个三元组
(
i
,
j
,
k
)
(i,j,k)
(i,j,k) ,
然后轮换它们的顺序,即
A
[
i
]
=
A
[
j
]
,
A
[
j
]
=
A
[
k
]
,
A
[
k
]
=
A
[
i
]
A[i]=A[j],A[j]=A[k],A[k]=A[i]
A[i]=A[j],A[j]=A[k],A[k]=A[i]
求在
m
m
m 次操作内得到第二个排列的概率,用
p
∗
q
−
1
m
o
d
998244353
p*q^{-1} \ mod \ 998244353
p∗q−1 mod 998244353 表示
n
≤
14
n \le 14
n≤14
m
≤
1
0
9
m \le 10^9
m≤109
分析
P25
不会写只能暴力搜索。
概率学说每使用一次操作的概率为
(
C
n
3
)
−
1
(C_n^3)^{-1}
(Cn3)−1。
枚举每一种操作水到10分,复杂度为
O
(
n
3
m
)
O({n^{3}}^m)
O(n3m)。
如果加上记忆化就能水到25分,复杂度为
O
(
n
!
∗
n
3
∗
m
)
O(n!*n^3*m)
O(n!∗n3∗m)。
看起来非常地令人尴尬。
P35
在上述的朴素方法上加以扩展,
对于
n
≤
5
n \le 5
n≤5的数据,将记忆化搜索改为DP,
然后用矩阵记录一下转移,
之后使用矩阵乘法得出答案。
P100
切分都不会,还是直接说正解吧。
根据一个早上的乱七八糟的打图打表找规律,
以及题目【三元轮换】的暗示。
然后发现用两个状态【单个数对应】【两个数轮换对应】可以满足
n
≤
5
n \le 5
n≤5内同状态序列转移到的状态相同,
再加一个状态【三个数轮换对应】满足了
n
≤
7
n \le 7
n≤7的情况。
猜想最终的状态是
n
n
n维的,即把排列划分为若干个轮换对应的数的集。
此时这个状态的转移图是完全正确的。
正常的DP或者枚举就可以计算出对于
n
=
14
n=14
n=14,这样的划分只有
135
135
135种。
又由于每个状态可能对应很多序列,需要将第二个序列映射为
1
,
2
,
3
,
⋯
 
,
n
1,2,3, \cdots ,n
1,2,3,⋯,n来保证其的唯一对应。
对于每种状态,随意生成一个符合该状态的序列,
然后
O
(
n
3
)
O(n^3)
O(n3)暴力求其转移的矩阵。
用一个小技巧将矩阵中的答案累加起来。
然后矩阵乘计算即可。
####优化
对于一个状态映射到一个ID,某方便的方法是直接用
map<vector<int>,int>
然而vector的自带比较函数就和字符串比较一样慢,
所以用Trie树或者是Hash代替均可。
另外,整个转移图其实被分成了两个部分。
可以只求关于答案的那一半,这样矩阵的长宽都能减小一半。
时间复杂度能优化到
O
(
6
7
3
∗
l
o
g
n
+
67
∗
n
4
)
O(67^3 *log \ n+67*n^4)
O(673∗log n+67∗n4)
####代码
这份代码是真的丑,各种数组和函数瞎丢。
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(register int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(register int i=(a),i##_end_=(b);i>i##_end_;i--)
#define INF 0x3f3f3f3f
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define Mod 998244353
#define Mul(a,b) (1ll*(a)*(b)%Mod)
#define Add(a,b) ((a+=b)%=Mod)
void Rd(int &res){
char c;res=0;
while((c=getchar())<48);
do res=(res<<3)+(res<<1)+(c^48);
while((c=getchar())>47);
}
#define ULL unsigned long long
#define M 24
#define S 69
#define SqMod 10961409671260274699ull
int n,m,A[M],PA[M],PB[M];
int Val;
vector<int>E[S<<1];
struct Mat{
ULL V[S][S];
void Clear(){memset(V,0,sizeof(V));}
Mat friend operator *(const Mat &A,const Mat &B){
Mat C;C.Clear();
REP(i,0,S) REP(j,0,S) if(A.V[i][j]) REP(k,0,S) if(B.V[j][k]){
C.V[i][k]+=A.V[i][j]*B.V[j][k];
if(C.V[i][k]>=SqMod)
C.V[i][k]-=SqMod;
}
REP(i,0,S) REP(j,0,S) if(C.V[i][j]) C.V[i][j]%=Mod;
return C;
}
}K,Res;
int Inv(int x){
int Res=1,k=x,p=Mod-2;
for(;p;k=Mul(k,k),p>>=1)if(p&1)Res=Mul(Res,k);
return Res;
}
int Use[M],id,st,ed;
int Zs[M];
map<ULL,int>ID;
ULL Num(int x,int Z[M]){
ULL p=0;
REP(i,0,x)p=p*233+Z[i];
return p;
}
int ToZs(int A[M]){
bool Vis[M];
static int Pos[M];
int len=0;
memset(Vis,0,sizeof(Vis));
REP(i,0,n)Pos[A[i]]=i;
REP(i,0,n)if(!Vis[i]){
int j=0,p=i;
do{
j++;
Vis[p]=1;
p=Pos[p+1];
}while(p!=i);
Zs[len++]=j;
}
sort(Zs,Zs+len);
return len;
}
void DFS(int v,int l,int x,bool k){
if(v==0){
if(!k)ID[Num(x,Use)]=id++;
else{
int st=ID[Num(x,Use)];
REP(i,0,n)A[i]=i+1;
int j=0,Tmp;
REP(i,0,x){
Tmp=A[j];
REP(k,1,Use[i])
A[j]=A[j+1],j++;
A[j++]=Tmp;
}
REP(a,0,n) REP(b,0,n) if(a!=b) REP(c,0,n) if(a!=c && b!=c){
Tmp=A[a],A[a]=A[c],A[c]=A[b],A[b]=Tmp;
E[st].push_back(ID[Num(ToZs(A),Zs)]);
Tmp=A[a],A[a]=A[b],A[b]=A[c],A[c]=Tmp;
}
}
return;
}
REP(i,l,v+1){
Use[x]=i;
DFS(v-i,i,x+1,k);
}
}
int Q[S<<1];
int ReVis[S<<1],reid;
void BFS(){
int l,r;l=r=0;
Q[r++]=0;
memset(ReVis,-1,sizeof(ReVis));
ReVis[0]=reid++;
while(l<r){
int A=Q[l++],B;
REP(i,0,E[A].size()){
if(ReVis[B=E[A][i]]==-1)
Q[r++]=B,ReVis[B]=reid++;
if(A)Add(K.V[ReVis[A]][ReVis[B]],Val);
}
}
}
int Mark[M];
int main(){
Rd(n),Rd(m);
Val=Inv(n*(n-1)*(n-2));
DFS(n,1,0,0);
DFS(n,1,0,1);
BFS();
REP(i,0,n)Rd(PA[i]);
REP(i,0,n)Rd(PB[i]),Mark[PB[i]]=i,PB[i]=i+1;
REP(i,0,n)PA[i]=Mark[PA[i]]+1;
st=ID[Num(ToZs(PA),Zs)];
if(ReVis[st]==-1){
puts("0");
return 0;
}
Res.V[0][ReVis[st]]=K.V[0][0]=1;
while(m){
if(m&1)Res=Res*K;
K=K*K;m>>=1;
}
cout<<Res.V[0][0];
return 0;
}
总之是过了,十分诡异的一道DP题。
这种状态的定义方法以前根本没有见过。
可以作为一个新的想法吧。
Date:2017-10-08
By Komachi