算法提升之數論(矩陣+快速冪)

通過矩陣和快速冪的方法來解決算法題目可以很好地降低時間復雜度,幫助大家更好地解決題目。

下面這道題目有一定難度,希望大家可以好好地理解,相信對大家會有很大的幫助。

問題描述

有 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
英文地址,請注明出處:http://en.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&…