洛谷4838 P哥破解密码(dp+矩乘优化)

题目链接

qwq一看数据范围就知道这个题的做法

首先我们先考虑一个朴素的dp
我们令 f [ i ] [ 0 / 1 / 2 ] f[i][0/1/2] f[i][0/1/2]表示到了第 i i i位,有连续 0 , 1 , 2 0,1,2 0,1,2 A A A的方案数。

显然,如果当前位置填 B B B
就相当于当前位置有0连续个A,那么前 i − 1 i-1 i1位填 0 , 1 , 2 0,1,2 0,1,2都是合法的

如果当前位置填 A A A
那么就相当于当前位置有 1 , 2 1,2 1,2个连续的A,分别能从前 i − 1 i-1 i1位有 0 , 1 0,1 0,1 A A A转移过来。

那么转移式子如下 f [ i ] [ 0 ] + = f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] + f [ i − 1 ] [ 2 ] f [ i ] [ 1 ] = f [ i − 1 ] [ 0 ] f [ i ] [ 2 ] = f [ i − 1 ] [ 1 ] f[i][0]+=f[i-1][0]+f[i-1][1]+f[i-1][2] \\ f[i][1]=f[i-1][0] \\f[i][2]=f[i-1][1] f[i][0]+=f[i1][0]+f[i1][1]+f[i1][2]f[i][1]=f[i1][0]f[i][2]=f[i1][1]

QWQ直接枚举的复杂度是 O ( n ) O(n) O(n)的,无法接受。

我们考虑优化…其实看到 n 能 从 n − 1 n能从n-1 nn1转移,很显然想到矩乘

构造如下矩阵

1 1 0 
1 0 1
1 0 0 

直接进行矩阵快速幂就好。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk make_pair
#define ll long long
#define int long long
using namespace std;
inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}
const int maxn = 10;
const int mod = 19260817;
struct Ju{
	int x,y;
	int a[maxn][maxn];
	Ju operator* (Ju b)
	{
		Ju ans;
		ans.x=x;
		ans.y=b.y;
		memset(ans.a,0,sizeof(ans.a));
		for (int i=1;i<=ans.x;i++)
		  for (int j=1;j<=ans.y;j++)
		    for (int k=1;k<=y;k++)
		      ans.a[i][j]=(ans.a[i][j]+a[i][k]*b.a[k][j]%mod)%mod;
		return ans;
	}
};
Ju qsm(Ju i,int j)
{
	Ju ans; 
	ans.x=i.x;
	ans.y=i.y;
	memset(ans.a,0,sizeof(ans.a));
	for (int p=1;p<=i.x;p++) ans.a[p][p]=1;
	while (j)
	{
		if(j&1) ans=ans*i;
		i=i*i;
		j>>=1;
	}
	return ans;
}
Ju a;
Ju b;
void init()
{
	memset(a.a,0,sizeof(a.a));
	a.x=1;
	a.y=3;
	a.a[1][1]=1;
	a.a[1][2]=1;
	a.a[1][3]=0;
	memset(b.a,0,sizeof(b.a));
	b.x=3;
	b.y=3;
	b.a[1][1]=1;
	b.a[1][2]=1;
	b.a[2][1]=1;
	b.a[2][3]=1;
	b.a[3][1]=1;
}
signed main()
{
  int m=read();
  while (m--)
  {
  	 int n=read();
  	 init();
  	 Ju tmp=qsm(b,n-1);
  	 a=a*tmp;
  	 cout<<(a.a[1][1]+a.a[1][2]+a.a[1][3])%mod<<endl;
  }
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值