【GDOI2017模拟12.3】告别

Description

给出两个1~n的排列A和B,可以进行m次操作,每次操作随机选择一个三元组(i,j,k),将这个三元组所对应的数在A中进行轮换(即i->j,j->k,k->i)
求在m次操作之内将A变成B的概率,答案对998244353取模
n<=14,m<=1e9

Solution

乍一看根本无法下手啊。。。
看到n辣么小m辣么大就知道是矩乘可是不会做。。。
如果n再小一点我们可以直接维护状态然后转移,但这样状态太多了
我们考虑怎样压缩状态数。。。
先把B对应成1~n的有序排列,然后我们就只需要对A进行轮换了。。。
一个很好的思路是我们可以用A中的所有置换群来表示A。。。
我们这样只用记录每个置换群的大小就好了,因为只需要本质不同的。。。
B唯一对应n个1,所以不用担心算重。。。
这样设的状态数是n的划分数,n=14时为135
转移的话先枚举状态,随便构造一个满足状态的排列,然后暴力枚举三元组转移,判断转移后的状态是哪一个,这个地方用hash/map/二分之类的就好了
然后把矩阵构造出来直接矩阵乘法就好了

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int N=15,M=135,mo=998244353;
int a[N],c[N],w[N],h[N],x,s,n,m,tot;
bool bz[N];
struct note{
    int a[N];
    friend bool operator == (note x,note y) {
        fo(i,1,n) if (x.a[i]!=y.a[i]) return 0;
        return 1;
    }
    friend bool operator < (note x,note y) {
        fo(i,1,n) if (x.a[i]<y.a[i]) return 1;
        else if (x.a[i]>y.a[i]) return 0;
        return 1;
    }
}q[M],d;
struct matrix{
    int a[M][M];
    friend matrix operator * (matrix y,matrix z) {
        matrix x;memset(x.a,0,sizeof(x.a));
        fo(i,0,tot) fo(j,0,tot) fo(k,0,tot) (x.a[i][j]+=(ll)y.a[i][k]*z.a[k][j]%mo)%=mo;
        return x;
    }
}g,t;
void dfs(int x,int la) {
    if (x==n) {
        memcpy(q[tot++].a,d.a,sizeof(d.a));
        return;
    } 
    fo(i,la,n-x) d.a[i]++,dfs(x+i,i),d.a[i]--;
}
int mi(int x,int y) {
    int z=1;
    for(;y;y/=2,x=(ll)x*x%mo) if (y&1) z=(ll)z*x%mo;
    return z;
}
note displace(int *a) {
    memset(bz,0,sizeof(bz));
    note A;memset(A.a,0,sizeof(A.a));
    fo(i,1,n) if (!bz[i]) {
        int x=i,size=0;
        while (!bz[x]) bz[x]=1,x=a[x],size++;
        A.a[size]++;
    }
    return A;
}
int find(note x) {
    int l=0,r=tot;
    while (l<r) {
        int mid=(l+r)/2;
        if (q[mid]<x) r=mid;
        else l=mid+1;
    }
    return l;
}
int main() {
    freopen("goodbye.in","r",stdin);
    freopen("goodbye.out","w",stdout);
    scanf("%d%d",&n,&m);dfs(0,1);tot--;
    int ni=mi(mi(n*(n-1)*(n-2),m),mo-2);
    fo(i,1,n) scanf("%d",&a[i]),w[a[i]]=i;
    fo(i,1,n) {
        scanf("%d",&x);
        a[w[x]]=i;
    }
    note A=displace(a);g.a[0][0]=n*(n-1)*(n-2);
    fo(i,0,tot) if (q[i]==A) {s=i;break;}
    fo(i,1,tot) {
        memset(c,0,sizeof(c));int len=0;
        fo(j,1,n) fo(k,1,q[i].a[j]) {
            fo(l,len+1,len+j-1) c[l]=l+1;
            c[len+j]=len+1;len+=j;
        }
        fo(j,1,n) fo(k,1,n) if (j!=k) 
            fo(l,1,n)  if (j!=l&&k!=l) {
                swap(c[j],c[k]);swap(c[j],c[l]);
                note C=displace(c);
                g.a[i][find(C)]++;
                swap(c[j],c[l]);swap(c[j],c[k]);
            }
    }
    memcpy(t.a,g.a,sizeof(t.a));
    for(m--;m;m/=2,g=g*g) if (m&1) t=t*g;
    printf("%lld\n",(ll)t.a[s][0]*ni%mo);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值