【树上倍增】【内向基环树】【 图论 】2836. 在传球游戏中最大化函数值

本文主要是介绍【树上倍增】【内向基环树】【 图论 】2836. 在传球游戏中最大化函数值,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文涉及知识点

树上倍增 内向基环树 图论

LeetCode2836. 在传球游戏中最大化函数值

给你一个长度为 n 下标从 0 开始的整数数组 receiver 和一个整数 k 。
总共有 n 名玩家,玩家 编号 互不相同,且为 [0, n - 1] 中的整数。这些玩家玩一个传球游戏,receiver[i] 表示编号为 i 的玩家会传球给编号为 receiver[i] 的玩家。玩家可以传球给自己,也就是说 receiver[i] 可能等于 i 。
你需要从 n 名玩家中选择一名玩家作为游戏开始时唯一手中有球的玩家,球会被传 恰好 k 次。
如果选择编号为 x 的玩家作为开始玩家,定义函数 f(x) 表示从编号为 x 的玩家开始,k 次传球内所有接触过球玩家的编号之 和 ,如果有玩家多次触球,则 累加多次 。换句话说, f(x) = x + receiver[x] + receiver[receiver[x]] + … + receiver(k)[x] 。
你的任务时选择开始玩家 x ,目的是 最大化 f(x) 。
请你返回函数的 最大值 。
注意:receiver 可能含有重复元素。
示例 1:

传递次数 传球者编号 接球者编号 x + 所有接球者编号
2
1 2 1 3
2 1 0 3
3 0 2 5
4 2 1 6

输入:receiver = [2,0,1], k = 4
输出:6
解释:上表展示了从编号为 x = 2 开始的游戏过程。
从表中可知,f(2) 等于 6 。
6 是能得到最大的函数值。
所以输出为 6 。
示例 2:

传递次数 传球者编号 接球者编号 x + 所有接球者编号
4
1 4 3 7
2 3 2 9
3 2 1 10

输入:receiver = [1,1,1,2,3], k = 3
输出:10
解释:上表展示了从编号为 x = 4 开始的游戏过程。
从表中可知,f(4) 等于 10 。
10 是能得到最大的函数值。
所以输出为 10 。

提示:

1 <= receiver.length == n <= 105
0 <= receiver[i] <= n - 1
1 <= k <= 1010

树上倍增

记录各节点传球一次的接受者和积分。
然后计算传球两次的接受者和积分。
⋯ \cdots 4 次 ⋯ \cdots
⋯ \cdots 8 次 ⋯ \cdots
⋮ \vdots

代码

核心代码

class CPow2
{
public:CPow2(vector<int>& receiver, long long k): m_c(receiver.size()){long long tmp = k;int iPow2 = 0;for( ; iPow2 <= 63;iPow2++ ){if ((1LL << iPow2) == tmp){break;}tmp &= ~(1LL << iPow2);}m_vParent.assign(iPow2 + 1, vector<int>(m_c));m_vSum.assign(iPow2 + 1, vector<long long>(m_c));for (int i = 0; i < m_c; i++){m_vParent[0][i] = receiver[i];m_vSum[0][i] =  receiver[i];}for (int j = 1; j <= iPow2; j++){for (int i = 0; i < m_c; i++){const int next = m_vParent[j - 1][i];m_vParent[j][i] = m_vParent[j - 1][next];m_vSum[j][i] = m_vSum[j-1][i] + m_vSum[j-1][next];}}}long long Query(int cur, long long k){long long ans = 0;for (int i = 0; i < m_vParent.size(); i++){if ((1LL << i) & k){ans += m_vSum[i][cur];cur = m_vParent[i][cur];}}return ans;}const int m_c;
protected:	vector<vector<int>> m_vParent;vector<vector<long long>> m_vSum;
};
class Solution {
public:long long getMaxFunctionValue(vector<int>& receiver, long long k) {CPow2 pow(receiver, k);long long llMax = 0;for (int i = 0; i < pow.m_c; i++){llMax = max(llMax, i+pow.Query(i, k));}return llMax;}
};

测试用例

template<class T, class T2>
void Assert(const T& t1, const T2& t2)
{assert(t1 == t2);
}template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{if (v1.size() != v2.size()){assert(false);return;}for (int i = 0; i < v1.size(); i++){Assert(v1[i], v2[i]);}}int main()
{vector<int> receiver; long long k;{Solution sln;receiver = { 1,0 }, k = 10000000000;auto res = sln.getMaxFunctionValue(receiver, k);Assert(5000000001, res);}{Solution sln;receiver = { 2, 0, 1 }, k = 4;auto res = sln.getMaxFunctionValue(receiver, k);Assert(6, res);}{Solution sln;receiver = { 1,1,1,2,3 }, k =3;auto res = sln.getMaxFunctionValue(receiver, k);Assert(10, res);}
}

2023年8月

class CCycle
{
public:
CCycle(vector& receiver):m_receiver(receiver),m_c(receiver.size())
{
}
void Init(const unordered_set& setNotCycle)
{
CalCycleDis(setNotCycle);
CalCycleNode();
}
long long CalScore(int begin, long long needNode)
{
const int iCycleHead = m_mNodeToCycleHead[begin];
const long long llCycleNum = needNode / m_mCycleNodes[iCycleHead].size();//需要走多少整圈
needNode %= m_mCycleNodes[iCycleHead].size();//不足一圈部分
const long long llCycleScore = m_vDisScoreToCycelHead[m_receiver[iCycleHead]].second;//完整一圈的分数
if (0 == needNode)
{
return llCycleScore * llCycleNum;
}
const int iNodeNumToHead = m_vDisScoreToCycelHead[begin].first+1;//当前点到环首,共经过多少个点
long long llRet = m_vDisScoreToCycelHead[begin].second;
if (needNode > iNodeNumToHead )
{//过了环首后,还需要继续
const int iSubBegin = m_mCycleNodes[iCycleHead][needNode - iNodeNumToHead+1];
llRet += llCycleScore - m_vDisScoreToCycelHead[iSubBegin].second ;
}
else if( needNode < iNodeNumToHead)
{//没到环首
const int index = (m_mCycleNodes[iCycleHead].size() - (iNodeNumToHead - needNode - 1)) % m_mCycleNodes[iCycleHead].size();
const int iSubBegin = m_mCycleNodes[iCycleHead][index];
llRet -= m_vDisScoreToCycelHead[iSubBegin].second;
}
return llRet + llCycleScore* llCycleNum;
}
protected:
void CalCycleNode()
{
for (const auto& head : m_setCycelHead)
{
m_mCycleNodes[head].emplace_back(head);
m_mNodeToCycleHead[head] = head;
for (auto next = m_receiver[head]; next != head; next = m_receiver[next])
{
m_mCycleNodes[head].emplace_back(next);
m_mNodeToCycleHead[next] = head;
}
}
}
void CalCycleDis(const unordered_set& setNotCycle)
{
//环上各点到环首(编号最小的点)的距离
m_vDisScoreToCycelHead.assign(m_c, std::make_pair(-1, -1));
for (int i = 0; i < m_c; i++)
{
if (setNotCycle.count(i))
{
continue;//非环
}
DFSHead(i, i);
}
}
std::pair<int, long long> DFSHead(int cur, int head)
{
if (-1 != m_vDisScoreToCycelHead[cur].first)
{
return m_vDisScoreToCycelHead[cur];
}
if (cur == head)
{
m_setCycelHead.emplace(head);
m_vDisScoreToCycelHead[cur] = std::make_pair(0, cur);
DFSHead(m_receiver[cur], head);
return m_vDisScoreToCycelHead[cur];
}
const auto [nextDis, nextScore] = DFSHead(m_receiver[cur], head);
return m_vDisScoreToCycelHead[cur] = std::make_pair(nextDis + 1, nextScore + cur);
}
vector<std::pair<int, long long>> m_vDisScoreToCycelHead;//环上个点到环首(编号最小): 距离 和 分数(包括当前点、环首)
unordered_set m_setCycelHead;//环首
unordered_map<int, vector> m_mCycleNodes;//环上各点(按顺序存储)
unordered_map<int, int> m_mNodeToCycleHead;//各点对应环首
const int m_c;
const vector& m_receiver;
};

class Solution {
public:
long long getMaxFunctionValue(vector& receiver, long long k) {
m_c = receiver.size();
m_receiver = receiver;
k++;//点数比边数多1
//由于出度为1,所以没个联通区域只有一个环
//由于点数等于边数,故每个联通区域必定有一个环
//由于出度为1,所以进入环后,就只能在环上循环
//计算入度
vector vInDeg(m_c);
for (const auto& n : receiver)
{
vInDeg[n]++;
}
queue que;
for (int i = 0; i < m_c; i++)
{
if (0 == vInDeg[i])
{
m_setInDeg0.emplace(i);
que.emplace(i);
}
}
//通过拓扑排序判断那些点时环上
while (que.size()) {
const int cur = que.front();
que.pop();
m_setNotCycle.emplace(cur);
const int next = receiver[cur];
vInDeg[next]–;
if (0 == vInDeg[next])
{
que.emplace(next);
}
}

	//求各点到环上的次数,及距离m_vDisScoreToCycle.assign(m_c, std::make_tuple(- 1,-1,-1));for (int i = 0; i < m_c; i++){dfs(i);}m_vRet.assign(m_c,-1);CCycle cycle(receiver);cycle.Init(m_setNotCycle);//处理整个路径都在环上for (int i = 0; i < m_c; i++){if (m_setNotCycle.count(i)){continue;}		m_vRet[i] = cycle.CalScore(i, k);}//处理整个路径不在环上的点for (const auto cur : m_setInDeg0){dfsLen(cur, k);}//非环开始,环结束for (int i = 0; i < m_c; i++){if (-1 != m_vRet[i]){continue;}m_vRet[i] = get<1>(m_vDisScoreToCycle[i]) + cycle.CalScore(get<2>(m_vDisScoreToCycle[i]), k - get<0>(m_vDisScoreToCycle[i]));}return *std::max_element(m_vRet.begin(),m_vRet.end());
}
void dfsLen(int cur, const long long llNeedNode)
{	if (get<0>(m_vDisScoreToCycle[cur]) < llNeedNode){return ;}long llScoer = 0;int r = cur;for(int i = 0 ; i < llNeedNode;i++ ){llScoer += r;r = m_receiver[r];}m_vRet[cur] = llScoer;while (get<0>(m_vDisScoreToCycle[m_receiver[cur]]) >= llNeedNode){llScoer += r - cur;cur= m_receiver[cur];r = m_receiver[r];m_vRet[cur] = llScoer;}		
}
std::tuple<int,long long,int> dfs(int cur)
{auto& curRes = m_vDisScoreToCycle[cur];if (-1 != get<0>(curRes)){return curRes;}if (!m_setNotCycle.count(cur)){return curRes = std::make_tuple(0,0,cur);}	auto [iDis,iScore,iCycle] = dfs(m_receiver[cur]);return curRes = std::make_tuple(iDis+1,iScore+cur, iCycle);
}		
std::unordered_set<int> m_setNotCycle,m_setInDeg0;//非环上点; 入度为0的点
vector<tuple<int,long long,int>> m_vDisScoreToCycle;
std::unordered_map<int, int> m_mNodeToCycle;
vector<long long> m_vRet;
int  m_c;
vector<int> m_receiver;

};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关

下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

这篇关于【树上倍增】【内向基环树】【 图论 】2836. 在传球游戏中最大化函数值的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/875897

相关文章

【操作系统】信号Signal超详解|捕捉函数

🔥博客主页: 我要成为C++领域大神🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 ​ 如何触发信号 信号是Linux下的经典技术,一般操作系统利用信号杀死违规进程,典型进程干预手段,信号除了杀死进程外也可以挂起进程 kill -l 查看系统支持的信号

java中查看函数运行时间和cpu运行时间

android开发调查性能问题中有一个现象,函数的运行时间远低于cpu执行时间,因为函数运行期间线程可能包含等待操作。native层可以查看实际的cpu执行时间和函数执行时间。在java中如何实现? 借助AI得到了答案 import java.lang.management.ManagementFactory;import java.lang.management.Threa

SQL Server中,isnull()函数以及null的用法

SQL Serve中的isnull()函数:          isnull(value1,value2)         1、value1与value2的数据类型必须一致。         2、如果value1的值不为null,结果返回value1。         3、如果value1为null,结果返回vaule2的值。vaule2是你设定的值。        如

高仿精仿愤怒的小鸟android版游戏源码

这是一款很完美的高仿精仿愤怒的小鸟android版游戏源码,大家可以研究一下吧、 为了报复偷走鸟蛋的肥猪们,鸟儿以自己的身体为武器,仿佛炮弹一样去攻击肥猪们的堡垒。游戏是十分卡通的2D画面,看着愤怒的红色小鸟,奋不顾身的往绿色的肥猪的堡垒砸去,那种奇妙的感觉还真是令人感到很欢乐。而游戏的配乐同样充满了欢乐的感觉,轻松的节奏,欢快的风格。 源码下载

tf.split()函数解析

API原型(TensorFlow 1.8.0): tf.split(     value,     num_or_size_splits,     axis=0,     num=None,     name='split' ) 这个函数是用来切割张量的。输入切割的张量和参数,返回切割的结果。  value传入的就是需要切割的张量。  这个函数有两种切割的方式: 以三个维度的张量为例,比如说一

剑指offer(C++)--孩子们的游戏(圆圈中最后剩下的数)

题目 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去

神经网络第三篇:输出层及softmax函数

在上一篇专题中,我们以三层神经网络的实现为例,介绍了如何利用Python和Numpy编程实现神经网络的计算。其中,中间(隐藏)层和输出层的激活函数分别选择了 sigmoid函数和恒等函数。此刻,我们心中不难发问:为什么要花一个专题来介绍输出层及其激活函数?它和中间层又有什么区别?softmax函数何来何去?下面我们带着这些疑问进入本专题的知识点: 1 输出层概述 2 回归问题及恒等函数 3

神经网络第一篇:激活函数是连接感知机和神经网络的桥梁

前面发布的文章介绍了感知机,了解了感知机可以通过叠加层表示复杂的函数。遗憾的是,设定合适的、能符合预期的输入与输出的权重,是由人工进行的。从本章开始,将进入神经网络的学习,首先介绍激活函数,因为它是连接感知机和神经网络的桥梁。如果读者认知阅读了本专题知识,相信你必有收获。 感知机数学表达式的简化 前面我们介绍了用感知机接收两个输入信号的数学表示如下:

vscode python pip : 无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称

在vscode中控制台运行python文件出现:无法将"pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。 使用vscode开发python,需要安装python开发扩展: 本文已经安装,我们需要找的是python安装所在目录,本文实际路径如下: 如果在本文路径中没有此目录,请尝试在C盘中搜索 python,搜索到相关python目录后,点击Python 3.9进入目录,

【服务器08】之【游戏框架】之【加载主角】

首先简单了解一下帧率 FixedUpdate( )   >   Update( )   >   LateUpdate( ) 首先FixedUpdate的设置值 默认一秒运行50次 虽然默认是0.02秒,但FiexedUpdate并不是真的0.02秒调用一次,因为在脚本的生命周期内,FixedUpdate有一个小循环,这个循环也是通过物理时间累计看是不是大于0.02了,然后调用一次。有