试题 算法提高 翔集合+快速幂/矩阵快速幂

蓝桥杯练习系统上的一道题,翔集合
原题链接在这里
这道题最开始的思路是动态规划,我们来考虑n-1和n的情况,我们假设f(n)代表n个数时翔集合的个数,那它和f(n-1)之间的关系是怎样的呢?
可以这样想:f(n)可以分为n在集合里和n不在集合里两种情况。当n不在集合里时:很明显,这和f(n-1)的值是一样的。当n在集合里时:那么必不能取的是n-1和n-2,那这和f(n-3)有什么关系呢,可以在f(n-3)的每个集合里有个n,也就是说情况数是不变的,这又是一种情况,最后还就一个情况就是只取两个值的时候,前面的f(n-3)是在n-3的基础上追加一个n,现在的情况是从1-(n-3)取一个数与n组合,那么就是n-3种情况。
这样总结下来呢,就是我们的状态转移方程:f(n)=f(n-1)+f(n-3)+n-3
这个方程想的过程还是有点复杂的,但是想清楚之后写代码就会很简单了,不过这样做的话,一定会超时的!因为题给的n到了10^15!这样循环效率太低了,我们要想别的方法矩阵乘法
在讲矩阵乘法之前,我们要先补充一些知识点
(1)快速幂
快速幂,顾名思义,就是以快速的方法进行幂运算,举个例子:求2^150
我们知道,幂运算是指数爆炸的运算,所以我们为了效率,要做的就是尽可能的降幂,快速幂的原理就是:指数减,底数增,减少运算次数,降低时间复杂度
再举个例子;2^4是不是也可以写成 (2*2)^4/2 就是4^2
当然可以!

int quictpower(int n,int power)
{
	int temp=1;
	while(power)
	{
		if(power%2==0)
		{
			n*=n;
			power/=2;
		}
		else
		{
			temp*=n;
			n*=n;
			power/=2;
		}
	}
	return temp;
}

这段代码就是快速幂的基本思想,当然这段代码还有很大的优化空间,例如:if、else里的重复语句、用更加快速的位运算代替普通运算
不过我就不写了,知道意思即可,一般写成这样,时间复杂度已经不算高了
掌握了快速幂,下面我们来看
(2)矩阵快速幂
矩阵快速幂与上面的快速幂不同的地方只在于它的底数不是个数字而是个矩阵,至于矩阵的乘法怎么算,我就不赘述了,代码就写上的(一看就懂)
我们先来定义矩阵(其实很简单,矩阵在表达上就是普通的二维数组),这里我们用个绝构体,不过为什么不直接用数组呢(我是看其他博主都是用结构体定义的,说是方便,emm,那就是吧)

struct Matrix
{
	long long m[5][5];
};

这里的定义我直接为这道题服务了,用了long long和5,当然是可以换成其他的。
有了定义之后,就要来写一下矩阵的乘法运算(幂运算当然要用到乘法,不像普通数的乘法,矩阵这里我们要自己定义一下)

Matrix Mul(Matrix A,Matrix B)
{
	Matrix temp;
	for(int i=0;i<5;i++)
	{
		for(int j=0;j<5;j++)
		{
			temp.m[i][j]=0;
			for(int k=0;k<5;k++)
			{
				temp.m[i][j]+=A.m[i][k]+B.m[k][j];
			}
		}
	}
	return temp;
}

到这里,所有的准备工作都做完了,就剩下矩阵快速幂的运算了,我们仿照快速幂来写

Matrix QuickPower(Matrix A,long long power)
{
	Matrix temp;
	for(int i=0;i<5;i++)
	{
		for(int j=0;j<5;j++)
		{
				temp.m[i][j]=0;
		}
	}
	for(int i=5;i<5;i++)
	{
		temp.m[i][i]=1;
	}
	//这是要准备的temp是一个单位阵(对角线为1,其余为0,这是仿照快速幂中的temp为1单位长度1设定的,就是一个起始值)
	while(power)
	{
		if(power%2==0)
		{
			power/=2;
			A.m=Mul(A,A);
		}
		else
		{
			temp*=A.m;
			power/=2;
			A.m=Mul(A,A);
		}
	}
	return temp;
}

有了上面快速幂的对比,这里的矩阵快速幂就很好理解了(代码是我刚刚手敲的,没有运行,如果有错,不要打我)
到这里呢,关于本题的知识点,我们就铺垫结束了,下面就要开始做这道题了,把刚刚的状态转移方程带过来
f(n)=f(n-1)+f(n-3)+n-3
我们怎么把它用矩阵来表示呢,说实话,好难啊!我真的想不到,我是看别人博客学来的
那么现在我来搬运大佬的思想:
矩阵乘法:AB=C
我们想要一个n到n+1的转移(当然也可以是n-1到n)那么用矩阵乘法表示就是:AnB=An+1
现在要来找An:因为f(n)由f(n-1)、f(n-3)、n-3得到,为了n-3转移的方便,我们需要一个常数,为了f(n-3)转移的方便,我们需要f(-2)做个桥梁
所以!An=|f(n-1) f(n-2) f(n-3) n-3 1| ,一个1 5的矩阵
那An+1=|f(n) f(n-1) f(n-2) n-2 1|.
An通过乘B得到An+1,所以B是一个5 5的矩阵
B=|1 1 0 0 0|
|0 0 1 0 0|
|1 0 0 0 0|
|1 0 0 1 0|
|0 0 0 1 1|
这个B怎么来的,需要自己去理解(我发誓,我在写这篇博客之前还不是很明了,就在刚刚,写着写着,自己懂了!!!)
有了B,就呼之欲出了,来了来了
我们知道f(1)到f(4)都是等于0的,从f(5)才开始有数,An
B=An+1
那如果从A5开始呢:A5B^(n-4)=An+1
这又怎么了呢!我们观察An+1,它的第一列不就是我们要求的f(n)嘛
我们只要用A5
B^(n-4)的第一列不就是结果嘛!!!没错!就是这样
B^(n-4)不就是矩阵快速幂???我们刚刚学过了的
A5是什么?代进去=|f(4) f(3) f(3) 2 1|=|1 0 0 2 1|
所以!!!结果就有了~好了,讲清楚了
下面看代码吧(只要懂了思想,就很清晰了)

#include <bits/stdc++.h>

using namespace std;
struct Matrix
{
    long long m[5][5];
}ans;
Matrix E={0},B={0};
Matrix Mul(Matrix A,Matrix B)
{
    Matrix temp;
    //memset(temp.m,0,sizeof(temp.m));

    for(int i=0;i<5;i++)
    {
        for(int j=0;j<5;j++)
        {
            temp.m[i][j]=0;
            for(int k=0;k<5;k++)
            {
                temp.m[i][j]=(temp.m[i][j]+A.m[i][k]*B.m[k][j])%1000007;
            }
        }
    }
    return temp;
}
Matrix Quick(Matrix a,long long power)
{
    Matrix temp;
    for(int i=0;i<5;i++)
    {
        for(int j=0;j<5;j++)
        {
            temp.m[i][j]=0;
        }
    }
    for(int i=0;i<5;i++)
    {
            temp.m[i][i]=1;
    }
    while(power)
    {
        if(power%2==0)
        {
            a=Mul(a,a);
            power/=2;
        }
        else
        {
            temp=Mul(temp,a);
            a=Mul(a,a);
            power/=2;
        }
    }

    return temp;

}
int main()
{
    long long  n;
    cin>>n;
    if(n<4)
    {
        cout<<0<<endl;
        return 0;
    }

    Matrix B;
    for(int i=0;i<5;i++)
    {
        for(int j=0;j<5;j++)
        {
            B.m[i][j]=0;
        }
    }
    //memset(B.m,0,sizeof(B.m));
    B.m[0][0]=B.m[0][1]=B.m[1][2]=B.m[2][0]=B.m[3][0]=B.m[3][3]=B.m[4][3]=B.m[4][4]=1;
    ans=Quick(B,n-4);
    long long  answer=(ans.m[0][0]+2*ans.m[3][0]+ans.m[4][0])%1000007;
    cout<<answer<<endl;
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值