当前位置: 首页 > article >正文

洛谷 P1357 花园


题目传送门


暴力

思路

状态设计

d p i , j dp_{i, j} dpi,j 表示目前放到第 i i i 个花圃,且最近 m m m 个花圃的状态是 j j j C C C 形花圃用 1 1 1 P P P 形花圃用 0 0 0 表示)的方案数。

状态转移

当前状态为 j j j 时,上一个状态就是 j > > 1 j >> 1 j>>1 ( j > > 1 )   ∣   ( 1 < < ( m − 1 ) ) (j >> 1) \ | \ (1 << (m - 1)) (j>>1)  (1<<(m1))
因为当上一个状态转移到当前状态时,它会左移一位,并且挤掉最左边一位的状态。那么上一个状态的最左边的那一位就有 0 / 1 0/1 0/1 两种可能。
转移时再判断上一个状态是否合法,即 1 1 1 的个数是否不超过 k k k 个。

边界条件

由于它是一个环形花园,因此我们要枚举最初始 m m m 个花圃的状态。假设其状态为 s t a r t start start,那么边界就是 d p m , s t a r t = 1 dp_{m, start} = 1 dpm,start=1

答案统计

还是因为它是圆形花园,所以当初始 m m m 个花圃的状态固定时,它的贡献就是 d p n + m , s t a r t dp_{n + m, start} dpn+m,start(相当于转一圈转回来了),将其累加即可。

时间复杂度

O ( n × 2 2 m ) O(n \times 2 ^ {2m}) O(n×22m),可以通过 n ≤ 1 0 5 n \leq 10 ^ 5 n105 的数据。

代码
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxn = 1e5 + 7;
const int maxs = (1 << 5) + 7;
const int mod  = 1e9 + 7;

ll n;
int m, k, ms;
bool vis[maxs];
int dp[maxn][maxs], ans;
int main() {
	scanf("%lld%d%d", &n, &m, &k);
	ms = (1 << m) - 1;
	for (int st = 0; st <= ms; ++st) {  // 枚举最初始 m 个的状态
		// __builtin_popcount 用来得到 st 的二进制中 1 的个数
		if (__builtin_popcount(st) > k) continue;  
		memset(dp, 0, sizeof(dp));
		
		// 由于初始时 dp[m][st] = 1, 最后统计也是 dp[n + m][st]
		// 所以直接将整个数组向左平移 m 个位置 
		dp[0][st] = 1;  
		for (int i = 1; i <= n; ++i) {
			for (int s = 0; s <= ms; ++s) {
				if (__builtin_popcount(s) > k) continue;
				
				int pre1 = (s >> 1), pre2 = (s >> 1) | (1 << (m - 1));
				if (__builtin_popcount(pre1) <= k)
					dp[i][s] = (dp[i][s] + dp[i - 1][pre1]) % mod;
				if (__builtin_popcount(pre2) <= k)
					dp[i][s] = (dp[i][s] + dp[i - 1][pre2]) % mod;
			}
		}
		ans = (ans + dp[n][st]) % mod;
	}
	printf("%d\n", ans);
	return 0;
} 

优化

100 % 100 \% 100% 的数据范围时是 n ≤ 1 0 15 n \leq 10 ^ {15} n1015,肯定需要 O ( l o g ( n ) ) O(log(n)) O(log(n)) 级别的算法。
这又是在转移方面优化,所以可以想到用矩阵快速幂转移。

假设我们用 t r a n s j , k trans_{j, k} transj,k 表示状态 j j j 是否可以转移到状态 k k k(就是转移矩阵),那么转移方程就可以转换为:
d p i , j = ∑ k = 0 2 m − 1 d p i − 1 , k × t r a n s k , j dp_{i, j} = \sum_{k = 0}^{2^m-1} dp_{i - 1, k} \times trans_{k, j} dpi,j=k=02m1dpi1,k×transk,j
又发现当前行状态只与上一行状态有关,所以这就相当于 d p i = d p i − 1 × t r a n s dp_i = dp_{i - 1} \times trans dpi=dpi1×trans
更进一步地, d p n = d p 0 × t r a n s n dp_n = dp_0 \times trans^n dpn=dp0×transn
因此先计算好转移矩阵的 n n n 次方后,再乘以初始状态矩阵就好了。

时间复杂度

O ( l o g ( n ) × 2 3 m ) O(log(n) \times 2^{3m}) O(log(n)×23m)

代码
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxn = 1e5 + 7;
const int maxs = (1 << 5) + 7;
const int mod  = 1e9 + 7;

ll n;
int m, k, ms;
int ans;
struct matrix {
	int a[maxs][maxs];
	void init() {memset(a, 0, sizeof(a));}
	matrix operator * (const matrix& x) const {
		matrix res; res.init();
		for (int i = 0; i <= ms; ++i)
			for (int j = 0; j <= ms; ++j)
				for (int k = 0; k <= ms; ++k)
					res.a[i][j] = (res.a[i][j] + 1ll * a[i][k] * x.a[k][j] % mod) % mod;
		return res;
	} 
} trans;
matrix qpow(matrix x, ll y) {
	matrix res; res.init();
	for (int i = 0; i <= ms; ++i)
		res.a[i][i] = 1;
	for (; y; y >>= 1, x = x * x)
		if (y & 1) res = res * x;
	return res;
}
int main() {
	scanf("%lld%d%d", &n, &m, &k);
	ms = (1 << m) - 1;
	for (int s = 0; s <= ms; ++s) {
		if (__builtin_popcount(s) > k) continue;
		int pre1 = (s >> 1), pre2 = (s >> 1) | (1 << (m - 1));
		if (__builtin_popcount(pre1) <= k)
			trans.a[pre1][s] = 1;
		if (__builtin_popcount(pre2) <= k)
			trans.a[pre2][s] = 1;
	}
	trans = qpow(trans, n);
	for (int s = 0; s <= ms; ++s) {
		if (__builtin_popcount(s) > k) continue;
		matrix dp; dp.init(), dp.a[0][s] = 1;
		dp = dp * trans;
		ans = (ans + dp.a[0][s]) % mod;
	}
	printf("%d\n", ans);
	return 0;
}

http://www.kler.cn/a/586945.html

相关文章:

  • c语言zixue
  • Java基础编程练习第31题-String类和StringBuffer类
  • 【八股文】ArrayList和LinkedList的区别
  • 【Python 语法】排序算法
  • 个人博客系统测试报告
  • C++程序设计语言笔记——抽象机制:模板
  • eclipse-mosquitt之docker部署安装与使用
  • 现在有分段、句子数量可能不一致的中英文文本,如何用python实现中英文对照翻译(即每行英文对应相应的中文)
  • MySQL事务及索引复习笔记
  • Qt从入门到入土(十) -数据库操作--SQLITE
  • JAVA EE(10)——线程安全——synchronized JUC(java.util.concurrent) 的常见类 线程安全的集合类
  • 机器学习编译器(二)
  • Java中的访问修饰符有哪些
  • Swagger 从 .NET 9 中删除:有哪些替代方案
  • 洛谷 P4933 大师
  • LRU(最近最少使用)算法实现
  • 探索Maas平台与阿里 QWQ 技术:AI调参的魔法世界
  • 车载软件刷写工具vFlash --- 自动化接口(Automation API)应用简介
  • 德语A1学习
  • 批量ip反查域名工具