洛谷P4007:小 Y 和恐怖的奴隶主(期望、矩阵快速幂)

解析

不难发现有效的状态只有 S = C 11 3 = 165 S=C_{11}^3=165 S=C113=165 种。
同时,能支持 n = 1 0 18 n=10^{18} n=1018 的算法也不剩啥了,要么拉插,要么矩乘。
本题当然就是矩乘了,转移矩阵也较为显然。

然而,直接做的话复杂度是 O ( T S 3 log ⁡ n ) O(TS^3\log n) O(TS3logn) 的,只有 60pts。

然后就有一个很简单但效果非常显著的优化:每次不再像正常矩阵快速幂一样求出转移矩阵的 n n n 次方,而是预处理出所有 2 i 2^i 2i 的转移矩阵,然后把答案行矩阵和需要的转移矩阵分别乘即可,复杂度 O ( T S 2 log ⁡ n ) O(TS^2\log n) O(TS2logn)

不要僵化思维!

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;

const int N=2050;
const double eps=1e-10;
const int mod=998244353;
inline ll read(){
  ll x(0),f(1);char c=getchar();
  while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
  while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
  return x*f;
}

inline ll ksm(ll x,ll k){
  ll res(1);
  while(k){
    if(k&1) res=res*x%mod;
    x=x*x%mod;
    k>>=1;
  }
  return res;
}

int n,m,k;
struct matrix{
  ll a[205][205];
  int x,y;
  matrix(int X=0,int Y=0):x(X),y(Y){memset(a,0,sizeof(a));}  
};
matrix operator * (const matrix &u,const matrix &v){
  matrix res(u.x,v.y);
  for(int k=1;k<=u.y;k++){
    for(int i=1;i<=u.x;i++){
      ll tmp=u.a[i][k];
      for(int j=1;j<=v.y;j++){
	(res.a[i][j]+=tmp*v.a[k][j])%=mod;
      }
    }
  }
  return res;
}

int id[10][10][10],tot;
struct state{
  int x,y,z;
};
matrix mi[100];

matrix calc(ll k){
  matrix res(tot+1,tot+1);
  for(int i=1;i<=tot+1;i++) res.a[i][i]=1;
  int cnt(0);
  while(k){
    if(k&1) res=res*mi[cnt];
    cnt++;
    k>>=1;
  }
  return res;
}

signed main(){
#ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
#endif
  int T=read();m=read();k=read();
  for(int i=0;i<=k;i++){
    for(int j=0;i+j<=k;j++){
      for(int p=0;i+j+p<=k;p++){
	id[i][j][p]=++tot;
	//printf("(%d %d %d) id=%d\n",i,j,p)
	if(m<=2) break;
      }
      if(m<=1) break;
    }
  }
  matrix trans(tot+1,tot+1);
  for(int a=0;a<=k;a++){
    for(int b=0;a+b<=k;b++){
      for(int c=0;c<=k;c++){
	int add2=(m==2&&min(1,k-a-b-c)),add3=(m==3&&min(1,k-a-b-c)),now=id[a][b][c];
	if(!now) continue;
	ll p=ksm(a+b+c+1,mod-2);
	trans.a[tot+1][now]=trans.a[now][now]=p;
	if(a) trans.a[ id[a-1][b][c] ][now]=p*a%mod;
	if(b) trans.a[ id[a+1][b-1+add2][c+add3] ][now]=p*b%mod;
	if(c) trans.a[ id[a][b+1+add2][c-1+add3] ][now]=p*c%mod;	
      }
    }
  }
  trans.a[tot+1][tot+1]=1;
  int s;
  if(m==1) s=id[1][0][0];
  if(m==2) s=id[0][1][0];
  if(m==3) s=id[0][0][1];
  mi[0]=trans;
  for(int i=1;i<=60;i++) mi[i]=mi[i-1]*mi[i-1];
  matrix ori(1,tot+1);
  ori.a[1][tot+1]=1;
  while(T--){
    ll n=read();
    int cnt(0);
    matrix res=ori;
    while(n){
      if(n&1) res=res*mi[cnt];
      ++cnt;
      n>>=1;
    }
    printf("%lld\n",res.a[1][s]);
  }
  return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值