花园 洛谷 1357 状压DP+矩阵亏快速幂

本文介绍了一种针对特定条件下的环形花园布局算法,通过状态压缩动态规划与矩阵快速幂的方法,解决了大规模数据下的花园布局计数问题。

摘要生成于 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: 复制【样例输入1】10 5 3【样例输入2】6 2 1输出样例#1: 复制【样例输出1】458【样例输出2】18说明【数据规模】40%的数据中,N<=20;60%的数据中,M=2;80%的数据中,N<=10^5。100%的数据中,N<=10^15。分析:额,本来想刷数学题的,结果被这道题撞车了,实在好久没写过矩阵快速幂,于是各种qiao。首先80%不难想到状压dp,因为m小于5嘛,然后定义转移方程f[i][j],表示拿到第i个花时,后m个花的状态是j的方案数,现在考虑哪些状态可以转移,不难看出j状态可以由j>>2+1,j>>2+2^(m-1)+1(手动模拟一下吧),这两个状态转移过来于是打上标记con[i][j],于是可以写出一个n*2^(m-1)*2^(m-1)的转移方程,f[i][j]=f[i-1][k]+f[i][j]。这是80分的做法,现在n太大于是考虑优化,我们观察一下可以发现,后面的每种方案数其实就是前面的方案递推过去的,递推规则是不变的,且推了n次,然后手动玩了下样例,发现最后的方案数就等于最开始处理出来的con[i][j]的n次方,于是考虑矩阵快速幂(很裸的)。最后也是最玄学的地方,你可以玩个样例观察一下,发现答案就是矩阵的对角线和,至于为什么,可以这样考虑,我们最后算出的结果肯定是sigma(f[n+m][i]),而对角线上的点的值其实就对应每种状态i,于是考虑把它们加起来得到最优解。最后说一下,取模一定要勤快。

# include <iostream>
# include <cstdio>
# include <cmath>
# include <list>
# include <cstring>
# include <map>
# include <algorithm>
# include <queue>
using namespace std;
typedef long long ll;
ll read()
{
	register ll f=1,i=0;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') {i=(i<<3)+(i<<1)+ch-'0';ch=getchar();}
	return f*i;
}
const int mod=1000000007;
ll con[40][40],res[40][40],n,m,k,tmp[40][40];
bool vis[40];
inline void MUL(ll s[][40],ll t[][40]){
	memset(tmp,0,sizeof(tmp));
	for(int i=0;i<(1<<m);++i)
	    for(int j=0;j<(1<<m);++j)
	        for(int k=0;k<(1<<m);++k)
	            tmp[i][j]=(tmp[i][j]+s[i][k]*t[k][j])%mod;
	for(int i=0;i<(1<<m);++i)
	    for(int j=0;j<(1<<m);++j) t[i][j]=tmp[i][j];
}
inline void KUM(){
	while(n){
    	if(n&1)MUL(con,res);
    	n>>=1;MUL(con,con);
    }
}
int main(){
	n=read(),m=read(),k=read();
	for(int i=0;i<(1<<m);++i){
		int x=i,sum=0;
		while(x){if(x&1) sum++;x>>=1;}
		if(sum<=k){
			vis[i]=1;con[i>>1][i]=1;
			con[(i>>1)+(1<<(m-1))][i]=1;
		}
		res[i][i]=1;
	}
    KUM();ll ans=0;
    for(int i=0;i<(1<<m);i++)
        if(vis[i]) ans+=res[i][i]%mod;
    cout<<(ans%mod+mod)%mod<<endl;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值