Luogu1357——矩乘优化递推

针对一个特定的环形花园布局问题,本博客介绍了一种利用矩阵快速幂优化递推算法的方法,通过DFS预处理和断环为链的思想,解决了大规模数据下的高效计算问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

小L有一座环形花园,沿花园的顺时针方向,他把各个花圃编号为1~N(2<=N<=10^15)。他的环形花园每天都会换一个新花样,但他的花园都不外乎一个规则,任意相邻M(2<=M<=5,M<=N)个花圃中有不超过K(1<=K< M)个C形的花圃,其余花圃均为P形的花圃。
例如,N=10,M=5,K=3。则
CCPCPPPPCC 是一种不符合规则的花圃;CCPPPPCPCP 是一种符合规则的花圃。
请帮小L求出符合规则的花园种数Mod 1000000007
由于请您编写一个程序解决此题。

输入输出格式

输入格式:
一行,三个数N,M,K。

输出格式:
花园种数Mod 1000000007

输入输出样例

输入样例#1:10 5 3
输出样例#1:458
输入样例#2:6 2 1
输出样例#2:18

说明

【数据规模】

40%的数据中,N<=20;
60%的数据中,M=2;
80%的数据中,N<=10^5。
100%的数据中,N<=10^15。


这题N有10^15这么大,而M又只有5,所以肯定是要用类似快速幂的东西来优化的。而矩阵快速幂是一种很常见的优化DP/递推的方式。所以这题的关键就是推出转移矩阵。
因为M只有5,所以我们先用DFS求出M内所有符合要求的花圃的情况。而对于每种情况,他所能转移到的情况只有两种,所以我们可以用一个邻接矩阵来存储所有的相互推出的状况。我们断环为链,再把1~M的部分接在N后面,那么这时要求的相当于就是从1~M情况开始,转移N次,则最后状态与1~M相同的方案数有多少种。
我们会发现i转移到j,可以看作i先转移到一个状态k,再由k转移到j,这是一个满足结合律的floyed类型的式子,可以用矩阵快速幂来搞。于是直接用快速幂转移N次,最后看有多少种f[i][i]即可。
#include<bits/stdc++.h>
#define MD 1000000007
#define MAXN 64
#define ll long long
using namespace std;
ll read(){
    char c;ll x=0,y=1;while(c=getchar(),(c<'0'||c>'9')&&c!='-');
    if(c=='-') y=-1;else x=c-'0';while(c=getchar(),c>='0'&&c<='9')
    x=x*10+c-'0';return x*y;
}
ll n,m,k,lim,ans,pre[6],ok[MAXN],A[MAXN][MAXN],B[MAXN][MAXN],U[MAXN][MAXN];
void work(ll re,ll num){
    ok[re]=1;ll state=re>>1;
    A[state][re]=1;
    if(num==k&&!(re&1)) return;
    A[pre[m]+state][re]=1;
}
void dfs(ll x,ll num,ll re){
    if(x==m+1){work(re,num);return;}
    dfs(x+1,num,re);
    if(num<k) dfs(x+1,num+1,re|pre[x]);
}
void mul(ll a[][MAXN],ll b[][MAXN]){
    register ll i,j,k;
    for(i=0;i<=lim;i++)
     for(j=0;j<=lim;j++){
        ll res=0;
        for(k=0;k<=lim;k++) res=(res+a[i][k]*b[k][j])%MD;
        U[i][j]=res;
     }
    for(i=0;i<=lim;i++)
     for(j=0;j<=lim;j++) a[i][j]=U[i][j];
}
void power(){
    register ll i,j;
    for(i=0;i<=lim;i++)
     for(j=0;j<=lim;j++)
      if(i==j) B[i][j]=1;else B[i][j]=0;
    while(n){
        if(n&1) mul(B,A);
        mul(A,A);n>>=1;
    }
}
int main()
{
    pre[1]=1;for(ll i=2;i<=5;i++) pre[i]=pre[i-1]<<1;
    n=read();m=read();k=read();
    lim=(1<<m)-1;dfs(1,0,0);power();
    for(ll i=0;i<=lim;i++) if(ok[i]) ans+=B[i][i],ans%=MD;
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值