告别(是题

题目描述

给两个长度为 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 pq1 mod 998244353 表示
n ≤ 14 n \le 14 n14
m ≤ 1 0 9 m \le 10^9 m109

分析

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!n3m)
看起来非常地令人尴尬。

P35

在上述的朴素方法上加以扩展,
对于 n ≤ 5 n \le 5 n5的数据,将记忆化搜索改为DP,
然后用矩阵记录一下转移,
之后使用矩阵乘法得出答案。

P100

切分都不会,还是直接说正解吧。
根据一个早上的乱七八糟的打图打表找规律,
以及题目【三元轮换】的暗示。
然后发现用两个状态【单个数对应】【两个数轮换对应】可以满足 n ≤ 5 n \le 5 n5内同状态序列转移到的状态相同,
再加一个状态【三个数轮换对应】满足了 n ≤ 7 n \le 7 n7的情况。
猜想最终的状态是 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(673log n+67n4)
####代码
这份代码是真的丑,各种数组和函数瞎丢。

#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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值