题解:洛谷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. 经典SQL问题:Top 10%

    学生表: create table hy_student( id number(4,0) primary key, name nvarchar2(20) not null, score number( ...

  2. .NET多线程(Thread,ThreadPool,Task,Async与Await)

    .NET多线程是什么? 进程与线程 进程是一种正在执行的程序. 线程是程序中的一个执行流. 多线程是指一个程序中可以同时运行多个不同的线程来执行不同的任务. .NET中的线程 Thread是创建和控制 ...

  3. EMQ集群搭建实现高可用和负载均衡(百万级设备连接)

    一.EMQ集群搭建实现高可用和负载均衡 架构服务器规划 服务器IP 部署业务 作用 192.168.81.13 EMQTTD EMQ集群 192.168.81.22 EMQTTD EMQ集群 192. ...

  4. Oracle自动存储管理ASM

    参考资料: https://docs.oracle.com/cd/B19306_01/server.102/b14231/storeman.htm#ADMIN036 什么是ASM? ASM是Autom ...

  5. oldboy edu python full stack s22 day16 模块 random time datetime os sys hashlib collections

    今日内容笔记和代码: https://github.com/libo-sober/LearnPython/tree/master/day13 昨日内容回顾 自定义模块 模块的两种执行方式 __name ...

  6. 对比 Redis 中 RDB 和 AOF 持久化

    概念 Redis 是内存数据库,数据存储在内存中,一旦服务器进程退出,数据就丢失了,所以 Redis 需要想办法将存储在内存中的数据持久化到磁盘. Redis 提供了两种持久化功能: RDB (Red ...

  7. 微服务实战系列(四)-注册中心springcloud alibaba nacos

    1.场景描述 因要用到微服务,关于注册中心这块,与同事在技术原型上做了讨论,初步定的方案是使用:阿里巴巴的nacos+springcloud gateway,下面表格是同事整理的注册中心对比,以前用的 ...

  8. xss利用——BeEF#stage1

    全文概览 简介 BeEF( The Browser Exploitation Framework) 是由Wade Alcorn 在2006年开始创建的,至今还在维护.是由ruby语言开发的专门针对浏览 ...

  9. 提效工具-python解析xmind文件及xmind用例统计

    现状 每个公司都有一个维护测试case的系统,有自研的也有买的,比如QC, 禅道等等,QA往往习惯使用xmind等思维导图工具来编写测试用例,因为思路清晰,编写方便,那么这就有一个问题,大多公司要求所 ...

  10. python+pymysql访问mysql数据库

    今天跟大家分享两种场景的python连接MySQL方法: 场景一:连接远程MySQL 首先,安装pymysql:在命令行执行pip install pymysql指令. 然后,导入pymysql: i ...