算法提升之数论(矩阵+快速幂)

通过矩阵和快速幂的方法来解决算法题目可以很好地降低时间复杂度,帮助大家更好地解决题目。

下面这道题目有一定难度,希望大家可以好好地理解,相信对大家会有很大的帮助。

问题描述

有 n(2≤n≤10) 个玩家玩游戏,他们按 1 到 n 编号。第 i(1≤i≤n) 个玩家有 ti个喜欢的玩家,给出第 i个玩家喜欢的玩家的编号列表。

最初 1 号玩家拿着一朵花,游戏进行k(0≤k≤1018) 个回合,每个回合拿着花的人会把花等概率地送给自己喜欢的人之一,k 回合游戏后拿着花的人获胜。分别求 n 个人获胜的概率,对 109+7 取模。

输入格式

第一行,包括两个正整数 n,k,分别表示玩家人数和游戏轮数。

以下 n 行,每行首先有一个非负整数ti​(1≤ti​≤n),表示第 i个玩家有 ti​ 个喜欢的人。然后输入 ti​ 个互不相同的正整数,表示第 i个玩家喜欢的人的编号。

输出格式

共 n 行,每行一个正整数pi​(1≤i≤n) 表示 k 次游戏后第 i 个人拿着花的概率,对109+7 取模。

令 M=109+7,可以证明所求概率可以写成既约分数 pq​ 的形式,其中 p,q 均为整数且 q≢0(modM)。应输出整数 p×q−1(modM)。

输入案例:

4 1
2 2 4
1 2
2 2 4
1 1

输出案例:

0
500000004
0
500000004

代码部分:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;const ll p = 1e9 + 7;struct Mat
{int n, m;ll a[12][12];Mat(int x, int y, int t = 0){n = x, m = y;memset(a, 0, sizeof(a));if(t){for(int i = 0; i < min(n, m); i++)a[i][i] = 1;}}friend Mat operator * (const Mat &A, const Mat &B){Mat C(A.n, B.m, 0);for(int i = 0; i < A.n; i++)for(int j = 0; j < B.m; j++)for(int k = 0; k < A.m; k++)C.a[i][j] = (C.a[i][j] + (A.a[i][k] * B.a[k][j]) % p) % p;return C;}
};Mat qmit(Mat A, ll n)
{Mat ret(A.n, A.m, 1);while(n){if(n & 1){ret = ret * A;}A = A * A;n >>= 1;}return ret;
}ll qmit(ll a, ll b)
{ll ans = 1;while(b){if(b & 1){ans = ans * a % p;}a = a * a % p;b >>= 1;}return ans;
}ll inv(int a)
{return qmit(a, p-2);
}int main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int n; ll k; cin >> n >> k;Mat X(n, n, 0), P(n, 1, 1);for(int i = 0; i < n; i++){int t; cin >> t;for(int j = 0; j < t; j++){int x; cin >> x;X.a[x-1][i] = inv(t);   // 表示花从玩家 i 到玩家 x 的概率}}P = qmit(X, k) * P;for(int i = 0; i < n; i++)cout << P.a[i][0] << '\n';return 0;
}

其中:

我们有一个游戏,规则如下:

  • 玩家数量:共有 n 个玩家,编号从 1 到 n

  • 初始状态:开始时,1 号玩家持有花朵。

  • 回合规则:每一回合,持有花朵的玩家会等概率地将花送给自己喜欢的玩家之一。

  • 胜利条件:经过 k 回合后,当前持有花朵的玩家获胜。

  • 目标:计算每个玩家在 k 回合后获胜的概率,结果需要对 109+7 取模。

核心挑战

  • 问题规模k 的最大值可以达到1018,这意味着如果我们直接模拟每一回合的传递过程,时间复杂度会是 O(k),这在计算上是不可行的。

  • 解决方案:利用矩阵快速幂(Matrix Exponentiation)技术,将时间复杂度降低到 O(log⁡k)。具体来说,通过矩阵乘法来表示概率的转移过程。

矩阵表示概率转移

1. 转移矩阵 X 的定义

转移矩阵 X 是一个 n × n 的方阵,其中 X[a][b] 表示从玩家 b 传递到玩家 a 的概率:

  • 如果玩家 b 喜欢玩家 a,那么 X[a][b] = 1 / t_b,其中 t_b 是玩家 b 喜欢的人数(即玩家 b 的“出度”)。

  • 如果玩家 b 不喜欢玩家 a,那么 X[a][b] = 0

2. 初始概率向量 P

初始时只有 1 号玩家持有花朵,因此初始概率向量 P 是一个 n × 1 的列向量:

  • P[0][0] = 1(假设 1 号玩家对应索引 0,概率为 1)。

  • 其余元素为 0

矩阵快速幂的作用

概率转移具有线性性质:k 回合后的概率分布,等于初始概率向量乘以“转移矩阵的 k 次幂”。即:

最终概率=Xk×初始概率向量最终概率=Xk×初始概率向量

由于 k 非常大,直接计算 Xk 是不可行的。因此,我们使用矩阵快速幂技术:

  • 将指数 k 分解为二进制形式(例如 k=2m+2n+…)。

  • 通过 O(log⁡k)次矩阵乘法来计算 Xk。

数学符号的修正

在原始描述中,有一些数学符号可能无法正确显示。以下是修正后的符号表示:

  • 转移矩阵 X 是一个n×n 的矩阵,其中 Xa,b​ 表示从玩家 b传递到玩家 a 的概率。

  • 初始概率向量 P是一个n×1 的列向量,其中 P0​=1,其余 Pi​=0(假设玩家编号从 0 开始)。

  • 最终概率的计算公式为:

    最终概率=Xk⋅P

其中 Xk 表示矩阵 X 的 k 次幂,⋅⋅ 表示矩阵乘法。

代码解析:

矩阵的创建:

1. 快速创建普通矩阵(全 0)

当需要一个n×m的全 0 矩阵时,只需传入行数和列数,第三个参数默认取 0:

Mat X(n, n);  // 等价于 Mat X(n, n, 0),创建n×n的全0矩阵(用于转移矩阵)

这对应代码中初始化转移矩阵X的场景,转移矩阵初始时所有元素为 0,后续再根据概率填充非 0 值。

2. 快速创建单位矩阵

当需要单位矩阵(如矩阵快速幂的初始值)时,只需传入t = 1

Mat ret(A.n, A.m, 1);  // 创建与A同维度的单位矩阵

单位矩阵在矩阵乘法中的作用相当于数字乘法中的 “1”(任何矩阵乘以单位矩阵都等于自身),是快速幂算法的基础。如果没有这个参数,创建单位矩阵需要单独写循环初始化,代码会更繁琐。

1. 矩阵结构体 Mat
struct Mat {int n, m;  // 矩阵的行数(n)和列数(m)ll a[12][12];  // 存储矩阵元素(n≤10,12足够容纳)// 构造函数:初始化n行m列矩阵,t=1时为单位矩阵Mat(int x, int y, int t = 0) {n = x;m = y;memset(a, 0, sizeof(a));  // 初始化为全0if (t) {  // 单位矩阵:对角线元素为1,其余为0for (int i = 0; i < min(n, m); i++) {a[i][i] = 1;}}}// 矩阵乘法:A(n×m) * B(m×p) → C(n×p)friend Mat operator*(const Mat &A, const Mat &B) {Mat C(A.n, B.m, 0);  // 结果矩阵C的大小为A的行数×B的列数for (int i = 0; i < A.n; i++) {  // 遍历C的行for (int j = 0; j < B.m; j++) {  // 遍历C的列for (int k = 0; k < A.m; k++) {  // 累加中间维度// 每个元素C[i][j] = sum(A[i][k] * B[k][j]) mod pC.a[i][j] = (C.a[i][j] + (A.a[i][k] * B.a[k][j]) % p) % p;}}}return C;}
};

2.矩阵快速幂 qmit(Mat A, ll n)

Mat qmit(Mat A, ll n) {Mat ret(A.n, A.m, 1);  // 初始化为单位矩阵(乘法的" identity ")while (n) {  // 二进制分解n,循环log2(n)次if (n & 1) {  // 若当前二进制位为1,将A乘入结果ret = ret * A;}A = A * A;  // A自乘,相当于指数翻倍(如A^2 → A^4 → A^8...)n >>= 1;  // 右移一位,处理下一个二进制位}return ret;
}

3. 模运算与逆元

// 快速幂计算 a^b mod p(用于求逆元)
ll qmit(ll a, ll b) {ll ans = 1;  // 结果初始为1while (b) {if (b & 1) {  // 二进制位为1时,乘入当前aans = ans * a % p;}a = a * a % p;  // a自乘,指数翻倍b >>= 1;}return ans;
}// 计算a的逆元 mod p(费马小定理)
ll inv(int a) {return qmit(a, p-2);  // 逆元 = a^(p-2) mod p(p是质数)
}

关于为什么可以用这个方式来表示概率:

矩阵乘法的本质是对所有可能的中间路径的概率进行 “加权求和”,这恰好符合概率转移的线性叠加规则:

  • 一次矩阵乘法对应一次转移后的概率计算;
  • 矩阵的 k 次幂对应 k 次转移后的总概率;
  • 最终通过初始向量与矩阵幂的乘积,得到 k 回合后的概率分布。

这就是为什么转移概率可以用矩阵乘法表示 —— 它完美适配了 “多路径概率叠加” 的逻辑。

一、friend 函数的作用:定义矩阵乘法规则

friend Mat operator*(const Mat &A, const Mat &B) 是一个友元运算符重载函数,它的核心作用是:
为 Mat 类型(矩阵)定义 * 运算符的行为,使得两个 Mat 对象可以用 A * B 的形式进行乘法运算,就像普通整数相乘(3 * 5)一样自然。

没有这个函数时,A * B 会报错(编译器不知道如何处理两个自定义矩阵的乘法);有了这个函数,编译器就会将 A * B 自动转换为对 operator*(A, B) 的调用。

二、在哪里被使用?

在代码的矩阵快速幂部分,这个友元函数被多次隐式调用:

1. ret = ret * A;

这里的 ret * A 是两个 Mat 对象相乘,编译器会自动调用 operator*(ret, A),即我们定义的友元函数:

  • 传入参数:ret(左操作数)和 A(右操作数)。
  • 函数返回值:两个矩阵相乘的结果(Mat 类型),赋值给 ret
2. A = A * A;

同理,A * A 也会触发友元函数 operator*(A, A),计算矩阵 A 与自身的乘积,结果赋值给 A

好了今天的分享就到这里,希望大家多多关注,后续也将继续进行分享。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/91023.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/91023.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数学建模算法-day[14]

6.2 传染病预测问题 问题提出 世界上存在很多传染病&#xff0c;如何根据其传播机理预测疾病得传染范围及染病人数等&#xff0c;对传染病的控制意义十分重大。 1.指数传播模型 基本假设 (1) 所研究的区域是一封闭区域&#xff0c;在一个时期内人口总量相对稳定&#xff0c;不考…

Linux救援模式之简介篇

什么是救援模式&#xff1f;救援模式提供了一个最小的Linux环境&#xff0c;通常只加载最基本的系统组件&#xff0c;允许管理员&#xff1a;修复损坏的系统恢复丢失的文件修改配置文件重置密码检查磁盘错误重新安装引导加载程序如何进入救援模式&#xff1f;1. 通过GRUB菜单进…

C++20实战FlamingoIM开发

C++20 与 Flamingo IM 实例 C++20 引入了许多新特性,如概念(Concepts)、协程(Coroutines)、范围(Ranges)等。Flamingo IM 是一个即时通讯项目,结合 C++20 的特性可以提升代码的可读性和性能。以下是基于 C++20 和 Flamingo IM 的实例。 协程实现异步网络通信 使用 C…

FPGA实现SRIO高速接口与DSP交互,FPGA+DSP异构方案,提供3套工程源码和技术支持

目录1、前言&#xff1a;SRIO在FPGADSP架构中的作用工程概述免责声明2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的FPGADSP异构方案我这里已有的 GT 高速接口解决方案3、工程详细设计方案工程设计原理框图FPGA端工程源码FPGA端SRIO从…

记一次导出pdf表单引发的问题

需求&#xff1a;点击按钮&#xff0c;将相关内容生成pdf下载下来问题1&#xff1a;之前项目封装好的下载文件方法不携带token 我尝试新写了一个方法&#xff0c;携带token问题2&#xff1a;此时出现了跨域问题 我分别尝试在controller类上和方法上加CrossOrigin(origins “*”…

AI-调查研究-39-多模态大模型量化 微调与量化如何协同最大化性能与效率?

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; AI炼丹日志-30-新发布【1T 万亿】参数量大模型&#xff01;Kim…

基于Dify构建本地化知识库智能体:从0到1的实践指南

技术选型与方案设计 在企业级AI应用落地中&#xff0c;本地化知识库智能体已成为提升业务效率的核心工具。Dify作为低代码AI应用开发平台&#xff0c;结合RAG&#xff08;检索增强生成&#xff09;技术&#xff0c;可快速构建私有化智能问答系统。以下是关键技术选型与架构设计…

C++与C#实战:FFmpeg屏幕录制开发指南

基于FFmpeg使用C#和C++开发 以下是一些基于FFmpeg使用C#和C++开发的简单屏幕录制软件示例,涵盖不同平台和功能需求。这些示例可作为学习或项目开发的起点。 使用C++开发FFmpeg屏幕录制 基础屏幕录制(Windows) #include <libavcodec/avcodec.h> #include <libav…

「源力觉醒 创作者计划」_DeepseekVS文心一言代码简单测试

一起来轻松玩转文心大模型吧一文心大模型免费下载地址&#xff1a;https://ai.gitcode.com/theme/1939325484087291906小插曲发现自己的上一篇文章的被盗了&#xff0c;而且是在deepseek上检索资料发现的&#xff0c;最让我破防的点在于&#xff0c;它完完全全搬运我的文章&…

服务器数据恢复—RAID上层部署的oracle数据库数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某公司一台服务器上有一组由24块FC硬盘组建的raid。 服务器出现故障&#xff0c;无法正常工作。 经过初步检测&#xff0c;管理员发现导致服务器故障的原因是raid中有两块硬盘掉线&#xff0c;导致卷无法挂载。服务器数据恢复过程&…

链表迭代翻转|二分|状态压缩bfs|数学

&#x1f36d;lc2039.bfs空闲时间把网络抽象成图&#xff0c;用 BFS 算出 0 号节点到各节点的最短距离 d 。结合每个节点发消息的间隔 patience[v] &#xff0c;先算消息往返需要 2d 秒。再看 2d 和 patience[v] 的关系若 2d 能被 patience[v] 整除&#xff0c;最后一条消息已发…

Vulnhub 02-Breakout靶机渗透攻略详解

一、下载靶机 下载地址&#xff1a;https://download.vulnhub.com/empire/02-Breakout.zip 下载好后使用VM打开&#xff0c;将网络配置模式改为net&#xff0c;防止桥接其他主机干扰&#xff08;桥接Mac地址也可确定主机&#xff09;。 二、发现主机 使用nmap扫描没有相应的…

数据结构(5)单链表算法题(中)

一、合并两个有序链表 1、题目描述 https://leetcode.cn/problems/merge-two-sorted-lists 2、算法分析 这道题和之前的合并两个有序数组的思路很像&#xff0c;创建空链表即可&#xff0c;可以很轻松地写出如下代码。 /*** Definition for singly-linked list.* struct L…

园区网络搭建实验

跟着B站上的老师&#xff0c;用华为ensp模拟搭建了一个园区网络&#xff0c;感觉挺好玩的虽然老师说这个很简单&#xff0c;但还是比我公司里的拓扑复杂LSW3配置上行端口3/4配置为串口&#xff0c;下行端口1/2为access口用于连接终端[Huawei]vlan batch 10 20 --创建vlan [Hua…

【tips】小程序css ➕号样式

上传的时候一般页面显示的是加号。不用图片可以用样式实现&#xff1b;wxss&#xff1a; /* 加号 */ .plus-box {width: 91rpx;height: 91rpx;border-radius: 6rpx;background: rgba(204, 204, 204, 1);position: relative; /* 用于定位加号 */ }/* 水平线条 */ .plus-box::bef…

MCU中的GPIO(通用输入/输出)是什么?

MCU中的GPIO(通用输入/输出)是什么? GPIO(General-Purpose Input/Output,通用输入/输出)是微控制器(MCU)或嵌入式系统中的一种可编程数字接口,用于与外部设备进行简单的高低电平信号交互。它是最基础、最常用的外设之一,广泛应用于按键检测、LED控制、传感器通信等场…

echarts 之 datazoom Y轴缩放

如果想 y 轴也能够缩放&#xff0c;那么在 y 轴上也加上 dataZoom 组件const dataZoomY ref([{type: "slider",yAxisIndex: 0,startValue: 0,endValue: 9,filterMode: "empty",width: 10,height: "80%",showDataShadow: false,left: 5,},{type:…

(四)Python基础入门-核心数据结构

概览 列表操作&#xff08;增删改查/切片/推导式&#xff09;元组特性与不可变性字典操作&#xff08;键值对/嵌套字典&#xff09;集合运算&#xff08;交集/并集/差集&#xff09; Python的核心数据结构是编程的基石&#xff0c;本文将系统讲解列表、元组、字典和集合四大数…

FCN语义分割算法原理与实战

FCN语义分割算法原理与实战 本文若有舛误&#xff0c;尚祈诸君不吝斧正&#xff0c;感激不尽。 前提概要&#xff1a;所使用的材料来源 对应视频材料&#xff1a;FCN语义分割 虽然可能比较简单但是奠定了使用卷积神经网络做语义分割任务的基础。 语义分割&#xff1a;输入图片…

堆的理论知识

1 引入1.1 普通二叉树不适合用数组存储的原因普通二叉树的结构是 “不规则” 的 —— 节点的左右孩子可能缺失&#xff0c;且缺失位置无规律。 若用数组存储&#xff08;按 “层次遍历顺序” 分配索引&#xff0c;即根节点放索引 0&#xff0c;根的左孩子放 1、右孩子放 2&…