题解:洛谷P1357 花园

Description

小 L 有一座环形花园,沿花园的顺时针方向,他把各个花圃编号为 \(1∼n\)。花园 \(1\) 和 \(n\) 是相邻的。

他的环形花园每天都会换一个新花样,但他的花园都不外乎一个规则:任意相邻 \(m\) 个花圃中都只有不超过 \(k\) 个 C 形的花圃,其余花圃均为 P 形的花圃。

例如,若 \(n=10 , m=5 , k=3\) ,则

· CCPCPPPPCC 是一种不符合规则的花圃。

· CCPPPPCPCP 是一种符合规则的花圃。

请帮小 L 求出符合规则的花园种数对 \(1e9 + 7\) 取模的结果。

Hints

对于\(40\%\)的数据,\(n \leq 20\)

对于\(60\%\)的数据,\(m = 2\)

对于\(80\%\)的数据,\(n \leq 10 ^5\)

对于\(100\%\)的数据,\(2 \leq n \leq 10^{15},\ 2 \leq m \leq min(n,5),\ 1 \leq k < m\)

Algorithm

单看Description可能以为这是个纯数学题,然而上手就能发现这玩意不太可做。

其实,这题其实已经把算法写在脸上了。

首先可以观察到 \(m\) 的值很小,因此显然需要状态压缩。

接着容易发现 \(n\) 的量级对于状态压缩来说大得离谱,因此显然是矩阵加速递推做的。

不管计数DP有没有最优子结构,姑且也算DP吧,那么这题就是个矩阵加速的计数类环形状压DP

接下来把递推式搞出来就完了。

先给出一些约定:

我们考虑把放 C 当做 1 ,放 P 当做 0 ,将相邻 \(m\) 位的摆花方法当成一个二进制数压缩。

因为转移时左移操作会带来麻烦,所以把二进数的位置与原串摆法相反。

所有位运算符号均采用 C++ 中的样式。

Step 1

先考虑普通的状压DP:

我们用 dp[i - 1][j] 表示考虑到第 \(i - 1\) 个位置,后 \(m\) 位顺次摆花的状态为 \(j\) 时的方案数。

如果把第 \(i\) 位设为 0 ,那么显然有新状态 j' = j >> 1

如果把第 \(i\) 位设为 1 ,那么显然有新状态 j' = (j >> 1) | (1 << (m - 1))

那么 dp[i][j] 显然就是dp[i - 1][j >> 1]dp[i - 1][(j >> 1) | (1 << (m - 1))]求和得到的。

注意后者不一定合法,需要 __builtin_popcount() 判断一下。 //这是个统计二进制数中 1 的个数的函数,奇快无比。

最后,其实这题环的特征其实并不明显,

对于前 \(m\) 位状态为 t 的情况来说,我们只需要递推完事之后将 dp[n][t] 计入答案即可。

当然,容易注意到 dp[i][] 只与 dp[i - 1][] 有关,可以滚动优化一下:

细节的小技巧就不细说了,容易写出代码:

int ans = 0, ful = 1 << m, p = m - 1;
for(int t = 0; t != ful; ++t)
{
if(__builtin_popcount(t) > k) continue;
memset(dp, 0, sizeof(dp)); dp[0][t] = 1;
for(int i = 1; i <= n; ++i)
for(int j = 0; j <= ful; ++j)
{
dp[i & 1][j] = dp[i - 1 & 1][j >> 1];
if(__builtin_popcount((j >> 1) | (1 << p)) <= k)
dp[i & 1][j] += dp[i - 1 & 1][(j >> 1) | (1 << p)];
}
ans += dp[n & 1][t];
}

复杂度显然是 \(O(n\cdot 4^m)\) 的。

Step 2

接下来就需要构造递推矩阵了。

以 \(m = 2, k = 1\) 为例,我们有

dp[n][00] = dp[n - 1][00] + dp[n - 1][01];
dp[n][01] = dp[n - 1][10];
dp[n][10] = dp[n - 1][00] + dp[n - 1][01];
dp[n][11] = 0;

从而有转移方程:

\[\left[
\begin{matrix}
dp_{n,00} \\
dp_{n,01} \\
dp_{n,10} \\
dp_{n,11} \\
\end{matrix}
\right]
=
\left[
\begin{matrix}
1 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
1 & 1 & 0 & 0 \\
0 & 0 & 0 & 0
\end{matrix}
\right]
\left[
\begin{matrix}
dp_{n - 1,00} \\
dp_{n - 1,01} \\
dp_{n - 1,10} \\
dp_{n - 1,11} \\
\end{matrix}
\right]
\]

(\(\LaTeX\) 的矩阵也太难写了吧……)

类似地,对于具体的 \(m,k\) ,

我们可以使用和上面的递推程序相同的方法构造出类似的矩阵。

最后同样来考虑环的问题。

根据 Step 1 中的分析,答案实际上就是\(\sum dp[\ n\ ][\ j\ ]\),在我们这里又有

\[\left[
\begin{matrix}
dp_{n,00\dots0} \\
dp_{n,00\dots1} \\
\vdots \\
dp_{n,11\dots0} \\
dp_{n,11\dots1} \\
\end{matrix}
\right]
=
\begin{bmatrix}
1 & 1 & \cdots & 0 & 0 \\
0 & 0 & \cdots & 0 & 0 \\
\vdots & \vdots & \ddots & \vdots & \vdots \\
0 & 0 & \cdots\ & 0 & 0\\
0 & 0 & \cdots\ & 1 & 1 \\
\end{bmatrix}
^ n
\left[
\begin{matrix}
1\\
1\\
\vdots \\
0\\
0\\
\end{matrix}
\right]
\]

(特别应当注意,右侧列向量并非全 1 向量)

如果把右侧列向量表示出来乘上去再求和,自然能做,不过有些麻烦了。

这里实际上有个绝妙的性质:

考虑构造出来的矩阵 \(A\) , \(A_{i,j}\) 实际上表示了状态 \(j\) 可以向状态 \(i\) 转移。

而由于环首尾相连的特性,对于一个合法的状态(也就是右侧列向量中对应值为 1 的状态),

在 \(n\) 次转移后,该状态一定可以转移回原状态,也即是说 \(A_{i,i}\) 一定非零。

而在矩阵做了 \(n\) 次幂乘之后, \(A_{i,i}\) 也就是起始状态为 \(i\) 的方案数。

因此答案实际上即为 \(\sum A_{i,i}\)。

\(\sum A_{i,i} = \sum dp[\ n\ ][\ j\ ]\) 的代数证明留给读者。(这能证吗?不管了,我们读者肯定会)

最后写写代码:

#include<bits/stdc++.h>
using namespace std; #define cnt __builtin_popcount
#define rep(i, n) for(int i = 0; i != n; ++i)
typedef long long ll;
const int P = 1e9 + 7;
ll n, m, k; class Matrix {
private:
vector<vector<int> > num; public:
Matrix() {}
Matrix(int n) {
num.resize(n);
rep(i, n) num[i].resize(n, 0);
}
int &operator () (int i, int j) {
return num[i][j];
}
inline void print()
{
int len = num.size();
for(int i = 0; i != len; ++i)
{
printf("%d", num[i][0]);
for(int j = 1; j != len; ++j)
printf(" %d", num[i][j]);
puts("");
}
}
inline int solve()
{
int ans = 0, len = num.size();
for(int i = 0; i != len; ++i)
(ans += num[i][i]) %= P;
return ans;
}
friend Matrix operator * (Matrix a, Matrix b)
{
int len = a.num.size();
Matrix ret(len);
rep(k, len) rep(i, len) rep(j, len) {
(ret(i, j) += (1LL * a(i, k) * b(k, j)) % P) %= P;
}
return ret;
} friend Matrix operator ^ (Matrix a, ll n)
{
int len = a.num.size();
Matrix ret(len);
rep(i, len) ret(i, i) = 1;
while(n)
{
if(n & 1) ret = ret * a;
a = a * a, n >>= 1;
}
return ret;
}
}; int main()
{
cin >> n >> m >> k;
int ful = 1 << m, p = m - 1;
Matrix cal(ful); for(int t = 0; t != ful; ++t)
{
if(cnt(t) > k) continue; cal(t >> 1, t) = 1;
int sta = (t >> 1) | (1 << p);
if(cnt(sta) <= k) cal(sta, t) = 1;
} cout << (cal ^ n).solve() << endl; return 0;
}

这个代码非常之丑。最开始设计模板类翻车了,没有重构而是左改右改的……

总之不是好代码,不过反正是这个意思。

复杂度 \(O(log n \cdot 8 ^ m)\) 很容易过。

题解:洛谷P1357 花园的更多相关文章

  1. 洛谷 P1357 花园 解题报告

    P1357 花园 题目描述 小\(L\)有一座环形花园,沿花园的顺时针方向,他把各个花圃编号为\(1~N(2<=N<=10^{15})\).他的环形花园每天都会换一个新花样,但他的花园都不 ...

  2. 洛谷P1357 花园(状态压缩 + 矩阵快速幂加速递推)

    题目链接:传送门 题目: 题目描述 小L有一座环形花园,沿花园的顺时针方向,他把各个花圃编号为1~N(<=N<=^).他的环形花园每天都会换一个新花样,但他的花园都不外乎一个规则,任意相邻 ...

  3. 洛谷 P1357 花园

    题意简述 一个只含字母C和P的环形串 求长度为n且每m个连续字符不含有超过k个C的方案数 题解思路 由于\(m<=5\)所以很显然状压 但由于\(n<=10^{15}\).可以考虑用矩阵加 ...

  4. [洛谷P1357] 花园

    题目类型:状压\(DP\) -> 矩阵乘法 绝妙然而思维难度极其大的一道好题! 传送门:>Here< 题意:有一个环形花圃,可以种两种花:0或1. 要求任意相邻的\(M\)个花中1的 ...

  5. 洛谷教主花园dp

    洛谷-教主的花园-动态规划   题目描述 教主有着一个环形的花园,他想在花园周围均匀地种上n棵树,但是教主花园的土壤很特别,每个位置适合种的树都不一样,一些树可能会因为不适合这个位置的土壤而损失观赏价 ...

  6. 题解 洛谷P5018【对称二叉树】(noip2018T4)

    \(noip2018\) \(T4\)题解 其实呢,我是觉得这题比\(T3\)水到不知道哪里去了 毕竟我比较菜,不大会\(dp\) 好了开始讲正事 这题其实考察的其实就是选手对D(大)F(法)S(师) ...

  7. 题解 洛谷 P3396 【哈希冲突】(根号分治)

    根号分治 前言 本题是一道讲解根号分治思想的论文题(然鹅我并没有找到论文),正 如论文中所说,根号算法--不仅是分块,根号分治利用的思想和分块像 似却又不同,某一篇洛谷日报中说过,分块算法实质上是一种 ...

  8. 题解-洛谷P5410 【模板】扩展 KMP(Z 函数)

    题面 洛谷P5410 [模板]扩展 KMP(Z 函数) 给定两个字符串 \(a,b\),要求出两个数组:\(b\) 的 \(z\) 函数数组 \(z\).\(b\) 与 \(a\) 的每一个后缀的 L ...

  9. 题解-洛谷P4229 某位歌姬的故事

    题面 洛谷P4229 某位歌姬的故事 \(T\) 组测试数据.有 \(n\) 个音节,每个音节 \(h_i\in[1,A]\),还有 \(m\) 个限制 \((l_i,r_i,g_i)\) 表示 \( ...

随机推荐

  1. 从两表连接看Oracle sql优化器的效果

    select emp.*,dept.* from tb_emp03 emp,tb_dept03 dept where emp.deptno=dept.id -- 不加hint SQL> sele ...

  2. 在Oracle中十分钟内创建一张千万级别的表

    小表不会产生性能问题,大表才会.要练习SQL调优,还非得有大表不可.但数据不会自然产生,没有数据时如何创建一张千万级别的大表呢? 之前,我想用Oracle的批量插入语法去插入数据,此语法如下: INS ...

  3. 在 Windows 上安装 Composer

    a.去官网 getcomposer.org 下载安装程序 b.运行安装程序,需要开启三个扩展 openssl.curl.mbstring,没有开启的话 composer 也可以帮助开启:会自动将com ...

  4. 2018.12.30【NOIP提高组】模拟赛C组总结

    2018.12.30[NOIP提高组]模拟赛C组总结 今天成功回归开始做比赛 感觉十分良(zhōng)好(chà). 统计数字(count.pas/c/cpp) 字符串的展开(expand.pas/c ...

  5. Python数据类型方法整理

      前言:主要是对Python数据类型做一个整理,部分知识点源于<python3程序开发指南(第二版)>   一.Python的关键要素 1.1 要素1:数据类型  int类型 str类型 ...

  6. [Codeforces1174B]Ehab Is an Odd Person

    题目链接 https://codeforces.com/contest/1174/problem/B 题意 给一个数组,只能交换和为奇数的两个数,问最终能得到的字典序最小的序列. 题解 内心OS:由题 ...

  7. Docker实战(5)升级Docker版本后的报错

    出现情况:因我升级了Centos内核后docker服务无法开启,所做重装处理但还是无效,最终将docker服务做了升级,升级步骤我会放置下面,但在启动老版本容器又出现Error response fr ...

  8. 分布式系统监视zabbix讲解四之可视化

    图形 概述 随着大量的监控数据被采集到Zabbix中,如果用户可以以可视化的表现形式来查看发生了什么事情,那么和仅仅只有数字的表现形式比起来则更加轻松. 以下是进行图形设置的地方.图形可以一目了然地掌 ...

  9. openstack核心组件——neutron网络服务(8)

    云计算openstack核心组件——neutron网络服务(8)   一.neutron 介绍:   Neutron 概述 传统的网络管理方式很大程度上依赖于管理员手工配置和维护各种网络硬件设备:而云 ...

  10. maximo----对比竞品的优势,以及sp的优势

    众多资产密集型企业对eam产品关注度都很高,尤其是eam产品的功能差别,这与行业差别有直接关系,如电力行业.煤炭行业或石油行业等,各行有各行的运营特点,那么eam产品在共性的基础上定出存在细小差异.下 ...