题目及代码:大意就是给定一个数n,问使用数字1到n能组合出多少组同时满足下列两种情况的序列:
1.序列中的数字从第一项到第i项(1≤i≤n),满足单调递增或者递减;
2.序列中的数字从第i项到第n项满足单调递增或者递减;
官方题解给的是2^n-2(n≥2),n=1时为1。推到方法不明......
这里说下我的做法:首先将序列分为4种情况:1.单调递增的(A);2.单调递减的(B);3.单调递减后点掉递增的(C);4.单调递增后单调递减的(D)。
然后我们在找一下它们之间的关系,就以3推4为例:1.单调递增的:1 2 3,我们要把4加进来,只有三种情况4 1 2 3,1 2 4 3和1 2 3 4,我们看出第一个属于第三种情况,第二个属于第四种情况,最后一个属于第一种情况,也就是说由于4的进入第三种情况的序列增加了1,第4中情况的序列增加了1,而第一种保持不变。
2.单调递减的:3 2 1,与第一种情况相似,4 3 2 1,3 4 2 1和3 2 1 4,第三种情况的序列增加了1,第4中情况的序列增加了1,而第二种保持不变。
3.单调递减后点掉递增的:这样的序列如2 1 3,我们只能把4放在左边或者右边即4 2 1 3和2 1 3 4,即第三种情况变成了原来的2倍。
4.单调递增后单调递减的:如1 3 2,这样我们只能把4放在中间,而又分为两种情况,1 3 4 2和1 4 3 2,即第四种情况变成了原来的2倍。
综合下来,我们就能看出增加有一个数字其效果是:A=A‘=1,B=B’=1,C=2*C+2,D=2*D+2。
公式出来了,然后直接写矩阵直接矩阵快速幂加速求解就可以了。
但是这里会遇到一个问题,就是mod太大了,矩阵乘法会爆精度,这里要注意把乘法转化成加法来计算。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
typedef unsigned long long ll;
//乘法换成加法
ll multi(ll a,ll b,ll p)
{
ll ret=0;
while(b>0)
{
if(b&1)
{
ret=(ret+a)%p;
}
b>>=1;
a=(a<<1)%p;
}
return ret;
}
struct mat
{
ll t[3][3];
void set()
{
memset(t,0,sizeof(t));
}
}a,b;
mat multiple(mat a,mat b,ll n,ll p)
{
ll i,j,k;
mat temp;
temp.set();
for(i=0; i<n; i++)
for(j=0; j<n; j++)
{
if(a.t[i][j]!=0)
for(k=0; k<n; k++)
temp.t[i][k]=(temp.t[i][k]+multi(a.t[i][j],b.t[j][k],p))%p;
}
return temp;
}
mat quick_mod(mat b,ll n,ll m,ll p)
{
mat t;
t.set();
for(ll i=0; i<n; i++) t.t[i][i]=1;
while(m)
{
if(m&1)
{
t=multiple(t,b,n,p);
}
m>>=1;
b=multiple(b,b,n,p);
}
return t;
}
void init()
{
b.set();
b.t[0][0]=2;
b.t[1][1]=2;
b.t[2][0]=2;
b.t[2][1]=2;
b.t[2][2]=1;
}
int main()
{
ll n,k;
while(scanf("%I64d%I64d",&n,&k)!=EOF)
{
if(n==1)
{
printf("%I64d\n",1%k);
continue;
}
if(n==2)
{
printf("%I64d\n",2%k);
continue;
}
if(n==3)
{
printf("%I64d\n",6%k);
continue;
}
init();
a=quick_mod(b,3,n-3,k);
printf("%I64d\n",(2*((1+a.t[0][0]+a.t[0][1]+a.t[1][0]+a.t[1][1])%k)%k+a.t[2][0]+a.t[2][1])%k);
}
return 0;
}