珍珠项链

题目限制

  • 内存限制:125.00MB
  • 时间限制:1.00s
  • 标准输入输出

题目知识点

  • 动态规划 \(dp\)
  • 矩阵
    • 矩阵乘法
    • 矩阵加速
    • 矩阵快速幂

题目来源

「 洛谷 」P2768 珍珠项链


为了方便大家阅读通畅,题目可能略有改动,保证不会造成影响

题目

题目背景

小\(L\) 通过泥萌的帮助,成功解决了牛栏的修建问题

奶牛们觉得主人非常厉害,就再也不偷懒了:母牛们奋力挤奶、生娃

小\(L\) 也因此成为了一代富豪

但是一直困扰 小\(L\) 的就是单身问题

小\(L\) 经过长久的寻觅,小\(L\) 终于找到了一个心仪的漂亮妹子

于是,小\(L\) 打算在 \(5.20\) 那天给妹子一个惊喜

题目描述

小\(L\) 决定用 \(K\) 种珍珠为妹子做一串举世无双的珍珠垂饰

珍珠垂饰是由珍珠连接而成的,其长度可以认为就是珍珠垂饰上珍珠的个数

小\(L\) 现在腰缠万贯,每种珍珠他都拥有 \(N\) 颗

根据将珍珠垂饰打开后珍珠不同的排列顺序可以区别出不同种类的项链

现在,小\(L\) 好奇自己可以组成多少种长度为 \(1\) 至 \(N\) 且不同的珍珠垂饰

当然,为显富有,每串珍珠垂饰都要必须由 \(K\) 种珍珠连接而成

由于答案可能太大,只需要取模 1234567891

这一定难不倒聪明的你吧

如果你能帮 小\(L\) 解决这个问题,他会把最后的资产分给你一半哦

格式

输入格式

第一行输入一个整数 \(T\) ,表示测试数据的个数

  • 对于每组数据:
  • 输入占一行,包含两个整数 \(N\) 和 \(K\),用一个空格隔开

输出格式

  • 对于每组数据
  • 输出占一行,包含一个整数,表示项链的种类数

样例

样例输入

2
2 1
3 2

样例输出

2
8

提示

时间限制

对于 \(70\% \sim 100\%\) 的数据:时间限制 \(10ms\)

数据范围

对于 \(40\%\) 的数据:\(1<= N<= 1e5\),\(0 \leq K \leq 30\)

对于 \(100\%\) 的数据:\(T \leq 10\),\(1 \leq N \leq 1e9\),\(0 \leq K \leq 30\)


思路

题目所说的 珍珠项链长度为 \(i\) 的种类数 其实就是 用不同的方式连接出长度为 \(i\) 的珍珠项链方案数

我们可以先思考 \(dp\):\(dp[i][j]\) 表示 用 \(j\) 种珍珠 连接出 长度为 \(i\) 的珍珠项链的方案数

  • 很多读者在这里可能会有一个疑问:\(dp\) 的状态 \(dp[i][j]\),似乎没有考虑每种珍珠的数量
  • 因为每种珍珠的个数是 \(N\) 颗,所需要求的最大长度也是 \(N\);也就是说,可以只用 \(1\) 种珍珠连接出所有的项链,每种珍珠的数量都是足够的;因此,珍珠的数量是不会造成影响的,可以忽略不计

我们可以进一步推出 \(dp\) 的状态转移方程:\(dp[i][j] = dp[i - 1][j - 1] * (K - (j - 1)) + dp[i - 1][j] * j\)

  • \(dp[i - 1][j - 1] * (k - (j - 1))\):当 第 \(i\) 颗珍珠前面 \(i - 1\) 颗用过的珍珠种类不同 的时候,所连接出长度为 \(i\) 的珍珠的方案数

    • \(dp[i - 1][j - 1]\):用 \(j - 1\) 种珍珠 连接出 长度为 \(i - 1\) 的项链 的方案数
    • \(k - (j - 1)\):前面的 \(i - 1\) 颗珍珠 已经用了 \(j - 1\) 种珍珠,而 第 \(i\) 颗使用没有用过的珍珠,就会有 剩下的珍珠种数 种情况,即 \(k - (j - 1)\)
  • \(dp[i - 1][j] * j\):当 第 \(i\) 颗的珍珠的种类 是之前用过的时候,所连接出来的方案数
    • \(dp[i - 1][j]\):用 \(j\) 种珍珠 连接出 长度为 \(i - 1\) 的项链 的方案数
    • \(j\):前面 \(i - 1\) 颗珍珠用了 \(j\) 种,第 \(i\) 颗珍珠 还是使用 这 \(j\) 种中的某一种,就有 \(j\) 种情况

最后的答案就是 \(dp[1][K] + dp[2][K] + dp[3][K] + ··· + dp[N][K] = \sum_{i = 1}^{N} dp[i][K]\)


分析

如果这道题的数据较小的话,用 \(dp\) 就可以 \(AC\) 了,可惜 \(N\) 的范围太大了

这时候我们就要思考如何优化 \(dp\) 的时间复杂度了

\(dp\) 是在进行递推转移,不妨可以把 \(dp\) 放在矩阵里求解

我们设 \(ans[i] = dp[1][K] + dp[2][K] + ··· + dp[i][K]\),我们所求的答案就是 \(ans[N]\)

我们可以构造一个 原始矩阵

\[\begin{bmatrix}
& dp[1][1] & dp[1][2] & dp[1][3] & ··· & dp[1][K] & ans[0] & \\
\end{bmatrix}
\]

为了状态转移,我们需要将它变成:

\[\begin{bmatrix}
& dp[2][1] & dp[2][2] & dp[2][3] & ··· & dp[2][K] & ans[1] & \\
\end{bmatrix}
\]

继续进化成:

\[\begin{bmatrix}
& dp[3][1] & dp[3][2] & dp[3][3] & ··· & dp[3][K] & ans[2] & \\
\end{bmatrix}
\]

以此类推,直到求出 \(ans[N]\) 为止

构造的原始矩阵\(dp\) 的状态转移方程 中,我们可以推出 加速矩阵 (\(k\) 行 \(k\) 列)

\[\begin{bmatrix}
& 1 & k - 1 & 0 &0 & ··· & 0 & 0 & \\
& 0 & 2 & k - 2 &0 & ··· & 0 & 0 & \\
& 0 & 0 & 3 & k - 3 & ··· & 0 & 0 & \\
& ··· & ··· & ··· & ··· & ··· & 0 & 0 & \\
& ··· & ··· & ··· & ··· & ··· & 1 & 0 & \\
& 0 &0 &0 &0 &0 &k &1 & \\
& 0 &0 &0 &0 &0 &0 &1 & \\
\end{bmatrix}
\]

刚开始是 \(ans[0]\),答案是 \(ans[N]\),所以要乘以 \(N\) 个加速矩阵,可是仍然要超时

这时候就需要矩阵快速幂了:根据矩阵乘法的结合律,先把 \(N\) 个加速矩阵乘起来,再用 原始矩阵 乘以 这个得到的矩阵,就可以得到最终的答案了

(注意:矩阵1 \(*\) 矩阵2 不一定等于 矩阵2 \(*\) 矩阵1,所以不能乘反了)


代码

#include <cstdio>
#include <cstring> const long long MOD = 1234567891LL;
int T, N, K; const int MAXK = 30;
struct Matrix
{
long long Mat[MAXK + 5][MAXK + 5];
int R, C; Matrix()
{
for (int i = 1; i <= MAXK + 1; i++)
for (int j = 1; j <= MAXK + 1; j++)
Mat[i][j] = 0LL;
} void Read() const // 矩阵的输入
{
if (R < 1 || C < 1) return ;
for (int i = 1; i <= R; i++)
for (int j = 1; j <= C; j++)
scanf("%lld", &Mat[i][j]);
} void Write() const // 矩阵的输出
{
if (R < 1 || C < 1) return ;
for (int i = 1; i <= R; i++) {
for (int j = 1; j < C; i++)
printf("%lld ", Mat[i][j]);
printf("%lld\n", Mat[i][C]);
}
} Matrix operator * (const Matrix One) const // 重载矩阵的乘号
{
Matrix Res;
Res.R = R, Res.C = C;
for (int i = 1; i <= Res.R; i++)
for (int j = 1; j <= Res.C; j++)
for (int k = 1; k <= One.R; k++)
Res.Mat[i][j] = (Res.Mat[i][j] + Mat[i][k] * One.Mat[k][j]) % MOD;
return Res;
}
}A, B; Matrix Pow(Matrix One, long long k) // 矩阵快速幂
{
Matrix Res, cnt = One;
Res.R = K + 1, Res.C = K + 1;
for (int i = 1; i <= K + 1; i++)
Res.Mat[i][i] = 1LL; // 单位矩阵
for (int i = k; i >= 1; i >>= 1)
{
if (i & 1) Res = Res * cnt;
cnt = cnt * cnt;
}
return Res;
} void Init(int k) // 初始化 原始矩阵 和 加速矩阵
{
A.R = 1, A.C = k + 1;
A.Mat[1][1] = (long long)k; B.R = B.C = k + 1;
B.Mat[1][1] = 1LL;
for (int i = 2; i <= k; i++)
{
B.Mat[i][i] = (long long)i;
B.Mat[i - 1][i] = (long long)k + 1LL - i;
}
B.Mat[k][k + 1] = B.Mat[k + 1][k + 1] = 1LL;
} int main()
{
scanf("%d", &T);
while (T--)
{
scanf("%d %d", &N, &K);
Init(K);
Matrix ans = A * Pow(B, N);
printf("%lld\n", ans.Mat[1][K + 1]);
}
return 0;
}

「 洛谷 」P2768 珍珠项链的更多相关文章

  1. 「 洛谷 」P4539 [SCOI2006]zh_tree

    小兔的话 推荐 小兔的CSDN [SCOI2006]zh_tree 题目限制 内存限制:250.00MB 时间限制:1.00s 标准输入输出 题目知识点 思维 动态规划 \(dp\) 区间\(dp\) ...

  2. 「 洛谷 」P2151 [SDOI2009]HH去散步

    小兔的话 欢迎大家在评论区留言哦~ HH去散步 题目限制 内存限制:125.00MB 时间限制:1.00s 标准输入 标准输出 题目知识点 动态规划 \(dp\) 矩阵 矩阵乘法 矩阵加速 矩阵快速幂 ...

  3. 「区间DP」「洛谷P1043」数字游戏

    「洛谷P1043」数字游戏 日后再写 代码 /*#!/bin/sh dir=$GEDIT_CURRENT_DOCUMENT_DIR name=$GEDIT_CURRENT_DOCUMENT_NAME ...

  4. 「P4994」「洛谷11月月赛」 终于结束的起点(枚举

    题目背景 终于结束的起点终于写下句点终于我们告别终于我们又回到原点…… 一个个 OIer 的竞赛生涯总是从一场 NOIp 开始,大多也在一场 NOIp 中结束,好似一次次轮回在不断上演.如果这次 NO ...

  5. 「洛谷4197」「BZOJ3545」peak【线段树合并】

    题目链接 [洛谷] [BZOJ]没有权限号嘤嘤嘤.题号:3545 题解 窝不会克鲁斯卡尔重构树怎么办??? 可以离线乱搞. 我们将所有的操作全都存下来. 为了解决小于等于\(x\)的操作,那么我们按照 ...

  6. 「洛谷3338」「ZJOI2014」力【FFT】

    题目链接 [BZOJ] [洛谷] 题解 首先我们需要对这个式子进行化简,否则对着这么大一坨东西只能暴力... \[F_i=\sum_{j<i} \frac{q_iq_j}{(i-j)^2}-\s ...

  7. 「BZOJ2733」「洛谷3224」「HNOI2012」永无乡【线段树合并】

    题目链接 [洛谷] 题解 很明显是要用线段树合并的. 对于当前的每一个连通块都建立一个权值线段树. 权值线段树处理操作中的\(k\)大的问题. 如果需要合并,那么就线段树暴力合并,时间复杂度是\(nl ...

  8. 「洛谷3870」「TJOI2009」开关【线段树】

    题目链接 [洛谷] 题解 来做一下水题来掩饰ZJOI2019考炸的心情QwQ. 很明显可以线段树. 维护两个值,\(Lazy\)懒标记表示当前区间是否需要翻转,\(s\)表示区间还有多少灯是亮着的. ...

  9. 「洛谷5300」「GXOI/GZOI2019」与或和【单调栈+二进制转化】

    题目链接 [洛谷传送门] 题解 按位处理. 把每一位对应的图都处理出来 然后单调栈处理一下就好了. \(and\)操作处理全\(1\). \(or\)操作处理全\(0\). 代码 #include & ...

随机推荐

  1. DB2中SQL基本语句的操作

    完全转载自:http://blog.sina.com.cn/s/blog_67aaf4440100v01p.html --创建数据库 create database Etp; --连接数据库 conn ...

  2. 这几种实现线程的方法你一定要知道,月薪20k以上的面试都会问到

    实现线程的三种方式总结 最近有看到Java线程的实现相关问题,在此对线程实现方式做一个小小的总结,当做笔记,便于日后查看. 平时常用的线程方式有三种: (1).继承Thread类,并重写其run()方 ...

  3. 如何制作C语言基本数据类型的思维导图

    在使用C语言编写程序时,数据类型是一个非常重要的内容,任何一个不被重视的数据错误都会使编译器无法翻译,导致程序报错. 使用思维导图来梳理各个数据类型是一个很有效的记忆方法,接下来就为大家展示一下我用i ...

  4. Folx专业版任务计划功能详解

    Folx专业版的任务计划功能允许用户以时间表的方式,制定下载计划.按照预先设定的时间计划,Folx会在指定的时间段内,自动开启或停止下载任务. 另外,用户还可以设置自动关机功能.当计划下载任务停止时, ...

  5. keras中seq2seq实现

    这里只是简单的一个例子 输入序列 目标序列 [13, 28, 18, 7, 9, 5] [18, 28, 13] [29, 44, 38, 15, 26, 22] [38, 44, 29] [27, ...

  6. PHP获取数组中重复值的键值

    $array = array ( 0=>'a', 1=>'b', 2=>'a', 5=>'b', 6=>'c', 40=>'d' ); $keyarr =[];$r ...

  7. 【P2634】聪聪可可——点分治

    (题面来自Luogu) 题目描述 聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃.两个人都想玩儿电脑(可是他们家只有一台电脑)--遇到这种问题,一般情况下石头 ...

  8. Leetcode 周赛#202 题解

    本周的周赛题目质量不是很高,因此只给出最后两题题解(懒). 1552 两球之间的磁力 #二分答案 题目链接 题意 有n个空篮子,第i个篮子位置为position[i],现希望将m个球放到这些空篮子,使 ...

  9. 编程C语言进阶篇——自定义数据类型:共同体

    什么是"自定义数据类型"?顾名思义,就是用户可以随时在程序中自行定义新的数据类型.自定义数据类型时需要设置数据类型的名称及其成员.数据类型成员各属性的设置方法等同于变量设置时相应属 ...

  10. 【mq读书笔记】顺序消息

    注意异常情况导致整个消费无限重试 阻塞消费 mq支持局部消息顺序消费,可以确保同一个消息消费队列中的消息被顺序消费.看下针对顺序消息在整个消费过程中做的调整: 队列负载: DefaultMQPushC ...