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

题目描述 小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;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
矩阵计数是一道经典的组合数学问题,可以用状压DP来解决。以下是一份Python代码的参考实现。 首先,我们需要输入矩阵的行数和列数,以及每行和每列的限制数。然后,我们可以使用二进制数来表示每行和每列的状态,其中1表示该行或该列已经有了一个矩阵,0表示该行或该列还可以放置一个矩阵。 接下来,我们可以使用状压DP来计算矩阵的数量。我们可以定义一个三维数组dp,其中dp[i][j][s]表示在第i行,第j列,状态为s时的矩阵数量。可以通过枚举上一个状态s',来更新dp[i][j][s]。具体来说,如果s'与s在第i行和第j列上的状态都是0,则可以从dp[i][j-1][s']或dp[i-1][j][s']转移而来。如果s'与s在第i行或第j列上的状态不同,则不能转移。最后,dp[m][n][0]就是最终的答案。 下面是完整的代码实现: ```python n, m, k1, k2 = map(int, input().split()) # 行状态用二进制数表示 row_mask = [0] * n for i in range(n): row_mask[i] = int(''.join(input().split()), 2) # 列状态用二进制数表示 col_mask = [0] * m for j in range(m): col_mask[j] = int(''.join(input().split()), 2) # 初始化dp数组 dp = [[[0 for _ in range(1 << m)] for _ in range(m + 1)] for _ in range(n + 1)] dp[0][0][0] = 1 # 状压DP for i in range(1, n + 1): for j in range(m + 1): for s in range(1 << m): for sp in range(1 << m): # 如果s'与s在第i行和第j列上的状态都是0,则可以从dp[i][j-1][s']或dp[i-1][j][s']转移而来 if (sp & s) == 0 and (row_mask[i - 1] & sp) == 0 and (col_mask[j - 1] & sp) == 0: if j == 0: dp[i][1][sp] += dp[i - 1][m][s] else: dp[i][j + 1][sp] += dp[i][j][s] + dp[i - 1][j][s] # 如果s'与s在第i行或第j列上的状态不同,则不能转移 else: continue # 计算答案 ans = 0 for s in range(1 << m): if bin(s).count('1') == k2: ans += dp[n][m][s] print(ans % 998244353) ``` 其中,我们使用了Python内置的bin函数来将一个整数转换为二进制字符串,并使用count方法来计算其中1的个数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值