【图论】 【割点】 【双连通分类】LCP 54. 夺回据点

news2024/12/26 3:35:32

本文涉及知识点

图论 割点 双连通分类
割点原理及封装好的割点类

LeetCode LCP 54. 夺回据点

魔物了占领若干据点,这些据点被若干条道路相连接,roads[i] = [x, y] 表示编号 x、y 的两个据点通过一条道路连接。
现在勇者要将按照以下原则将这些据点逐一夺回:
在开始的时候,勇者可以花费资源先夺回一些据点,初始夺回第 j 个据点所需消耗的资源数量为 cost[j]
接下来,勇者在不消耗资源情况下,每次可以夺回一个和「已夺回据点」相连接的魔物据点,并对其进行夺回
注:为了防止魔物暴动,勇者在每一次夺回据点后(包括花费资源夺回据点后),需要保证剩余的所有魔物据点之间是相连通的(不经过「已夺回据点」)。
请返回勇者夺回所有据点需要消耗的最少资源数量。
注意:
输入保证初始所有据点都是连通的,且不存在重边和自环
示例 1:
输入: cost = [1,2,3,4,5,6] roads = [[0,1],[0,2],[1,3],[2,3],[1,2],[2,4],[2,5]]
输出:6
解释: 勇者消耗资源 6 夺回据点 0 和 4,魔物据点 1、2、3、5 相连通; 第一次夺回据点 1,魔物据点 2、3、5 相连通; 第二次夺回据点 3,魔物据点 2、5 相连通; 第三次夺回据点 2,剩余魔物据点 5; 第四次夺回据点 5,无剩余魔物据点; 因此最少需要消耗资源为 6,可占领所有据点。i
在这里插入图片描述

示例 2:

输入: cost = [3,2,1,4] roads = [[0,2],[2,3],[3,1]]
在这里插入图片描述

输出:2

解释: 勇者消耗资源 2 夺回据点 1,魔物据点 0、2、3 相连通; 第一次夺回据点 3,魔物据点 2、0 相连通; 第二次夺回据点 2,剩余魔物据点 0; 第三次夺回据点 0,无剩余魔物据点; 因此最少需要消耗资源为 2,可占领所有据点。image.png

提示:
1 <= roads.length, cost.length <= 105
0 <= roads[i][0], roads[i][1] < cost.length
1 <= cost[i] <= 109

预备知识

点双连通:删掉任意一个点之后及关联的边后,图仍联通。
点双连通分量的缩点

性质一: 无向图中,要么是树边,父亲指向孩子;要么是回边,后代指向祖宗。见:割点原理及封装好的割点类。
性质二:一个环一定是点双连通。
性质三:一个环+一个树,一定不是点双连通区域。令树的一边a,b不在环上,b不在环上。则删除a,b就成了新的连通区域。
性质四:两个环有一个点重合,一定不是点双连通。删除重合点就分开了。
性质五:两个环有两个或更多的顶点相同,则一定是双连通区域。令相同的顶点是a和b。删除a后,两个环余下的点,通过b连通。删除b,类似。
性质六:设G1,G2最大点双连通图,如果G1和G2包括相同的边ab,则G1 == G2。删除a后,G1和G2所有的点都可以通过b连通,故G1和G2可以合并。
性质七:无向图的任何树边都只属于一个强连通分量。

简化情况

假定是树,且节点数大于等于3。一定会存在度数>=2的节点,以任意度数为2的根,形成树。
令叶子数是k,选择k-2个叶子结束,无法完全占据。2个叶子节点最近公共祖先无法消除。
选择k-1个叶子,一定可以占据。设没选择的节点是a,k-1个消除到和a的公共祖先,余下的树都是a的祖先,从根开始消除。
不能选择根节点,除一个子树外,可以选择排除一个叶子节 点外的叶子节点;其它子树要全部选择。显然劣于k-1叶子节点。
不能选择枝节点,选了只有两个选择:
a,本子树全选,本子树外的叶子排除一个外全选。
b,本子树外的节点全选。本子树的叶子排除一个外全选。
显然劣于选择k-1个叶子节点。

在这里插入图片描述
红色字体是割点,红色线段是回边。
共四个点双连通区域,红色背景(节点0)、绿色背景(3)、紫色背景(6) 只和一个割点连接,消除不会形成新的连通区域。蓝色节点(4)和两个割点连通,首先删除会,让2和5断开。

点双连通区域占据任意点,任意剩余据点也是连通。

关于点双连通区域

性质一:除根节点外的任何节点x都会放到符合以下条件的next所在连通区域。
next就是x或next是x的祖宗。如果多个next,取级别最高的。
断开cur后,next和cur的祖宗不连通。每个节点只会入栈出栈一次。
性质二:next的父节点也会放到next所在的双连通区域。
性质三:除根节点外的任何节点x 都有符合性质一的next,根节点的子节点一定符合。

代码

两种特殊情况: 一个连通区域只有一个点。本类会解析错误,解析成没有点双连同区域。
没有割点,会解析成一个点双连通区域,解析正确。本题要特殊处理。

class CNeiBo
{
public:	
	static vector<vector<int>> Two(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0) 
	{
		vector<vector<int>>  vNeiBo(n);
		for (const auto& v : edges)
		{
			vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);
			if (!bDirect)
			{
				vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);
			}
		}
		return vNeiBo;
	}	
	static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
	{
		vector<vector<std::pair<int, int>>> vNeiBo(n);
		for (const auto& v : edges)
		{
			vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);
			if (!bDirect)
			{
				vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);
			}
		}
		return vNeiBo;
	}
	static vector<vector<int>> Grid(int rCount, int cCount, std::function<bool(int, int)> funVilidCur, std::function<bool(int, int)> funVilidNext)
	{
		vector<vector<int>> vNeiBo(rCount * cCount);
		auto Move = [&](int preR, int preC, int r, int c)
		{
			if ((r < 0) || (r >= rCount))
			{
				return;
			}
			if ((c < 0) || (c >= cCount))

			{
				return;
			}
			if (funVilidCur(preR, preC) && funVilidNext(r, c))
			{
				vNeiBo[cCount * preR + preC].emplace_back(r * cCount + c);
			}
		};

		for (int r = 0; r < rCount; r++)
		{
			for (int c = 0; c < cCount; c++)
			{
				Move(r, c, r + 1, c);
				Move(r, c, r - 1, c);
				Move(r, c, r, c + 1);
				Move(r, c, r, c - 1);
			}
		}
		return vNeiBo;
	}
};

//割点
class CCutPoint
{
public:
	CCutPoint(const vector<vector<int>>& vNeiB) : m_iSize(vNeiB.size())
	{
		m_vNodeToTime.assign(m_iSize, -1);
		m_vCutNewRegion.resize(m_iSize);		
	}
	void Init(const vector<vector<int>>& vNeiB)
	{
		for (int i = 0; i < m_iSize; i++)
		{
			if (-1 == m_vNodeToTime[i])
			{
				m_vRegionFirstTime.emplace_back(m_iTime);
				dfs(vNeiB, i, -1);
			}
		}
	}	
	const int m_iSize;
	const vector<int>& Time()const { return m_vNodeToTime; }//各节点的时间戳
	const vector<int>& RegionFirstTime()const { return m_vRegionFirstTime; }//各连通区域的最小时间戳
	vector<bool> Cut()const { 
		vector<bool> ret;
		for (int i = 0; i < m_iSize; i++)
		{
			ret.emplace_back(m_vCutNewRegion[i].size());
		}
		return ret; }//
	const vector < vector<pair<int, int>>>& NewRegion()const { return m_vCutNewRegion; };
protected:
	int dfs(const vector<vector<int>>& vNeiB, const int cur, const int parent)
	{
		int iMinTime = m_vNodeToTime[cur] = m_iTime++;
		OnBeginDFS(cur);
		int iRegionCount = (-1 != parent);//根连通区域数量
		for (const auto& next : vNeiB[cur]) {
			if (-1 != m_vNodeToTime[next]) {
				iMinTime = min(iMinTime, m_vNodeToTime[next]);
				continue;
			}
			const int childMinTime = dfs(vNeiB, next, cur);
			iMinTime = min(iMinTime, childMinTime);
			if (childMinTime >= m_vNodeToTime[cur]) {
				iRegionCount++;
				m_vCutNewRegion[cur].emplace_back(m_vNodeToTime[next], m_iTime);
				OnNewRegion(cur, next);
			}
		}
		if (iRegionCount < 2)
		{
			m_vCutNewRegion[cur].clear();
		}
		return iMinTime;
	}
	virtual void OnNewRegion(int cur, int next) {};
	virtual void OnBeginDFS(int cur) {};
	vector<int> m_vNodeToTime;
	vector<int> m_vRegionFirstTime;
	vector < vector<pair<int, int>>> m_vCutNewRegion; //m_vCutNewRegion[c]如果存在[left,r) 表示割掉c后,时间戳[left,r)的节点会形成新区域
	int m_iTime = 0;
};

class CConnectAfterCutPoint 
{
public:
	CConnectAfterCutPoint(const vector<vector<int>>& vNeiB) :m_ct(vNeiB)
	{
		m_vTimeToNode.resize(m_ct.m_iSize);
		m_vNodeToRegion.resize(m_ct.m_iSize);
		for (int iNode = 0; iNode < m_ct.m_iSize; iNode++)
		{
			m_vTimeToNode[m_ct.Time()[iNode]] = iNode;
		}
		for (int iTime = 0,iRegion= 0; iTime < m_ct.m_iSize; iTime++)
		{
			if ((iRegion < m_ct.RegionFirstTime().size()) && (m_ct.RegionFirstTime()[iRegion] == iTime))
			{
				iRegion++;
			}
			m_vNodeToRegion[m_vTimeToNode[iTime]] = (iRegion - 1);
		}
	}
	bool Connect(int src, int dest, int iCut)const
	{
		if (m_vNodeToRegion[src] != m_vNodeToRegion[dest])
		{
			return false;//不在一个连通区域
		}
		if (0 == m_ct.NewRegion()[iCut].size())
		{//不是割点
			return true;
		}
		const int r1 = GetCutRegion(iCut, src);
		const int r2 = GetCutRegion(iCut, dest);
		return r1 == r2;
	}
	vector<vector<int>> GetSubRegionOfCut(const int iCut)const
	{//删除iCut及和它相连的边后,iCut所在的区域会分成几个区域:父节点一个区域、各子节点		一个区域
			//父节点所在区域可能为空,如果iCut所在的连通区域只有一个节点,则返回一个没有节点的			区域。
		const auto& v = m_ct.NewRegion()[iCut];
		vector<int> vParen;
		const int iRegion = m_vNodeToRegion[iCut];
		const int iEndTime = (iRegion + 1 == m_ct.RegionFirstTime().size()) ? m_ct.m_iSize : m_ct.RegionFirstTime()[iRegion+1];
		vector<vector<int>> vRet;	
		for (int iTime = m_ct.RegionFirstTime()[iRegion],j=-1; iTime < iEndTime; iTime++)
		{
			if (iCut == m_vTimeToNode[iTime])
			{
				continue;
			}
			if ((j + 1 < v.size()) && (v[j + 1].first == iTime))
			{
				j++;
				vRet.emplace_back();
			}
			const int iNode = m_vTimeToNode[iTime];
			if ((-1 != j ) && (iTime >= v[j].first) && (iTime < v[j].second))
			{
				vRet.back().emplace_back(iNode);
			}
			else
			{
				vParen.emplace_back(iNode);
			}			
		}
		vRet.emplace_back();
		vRet.back().swap(vParen);
		return vRet;
	}	
protected:
	int GetCutRegion(int iCut, int iNode)const
	{
		const auto& v = m_ct.NewRegion()[iCut];
		auto it = std::upper_bound(v.begin(), v.end(), m_ct.Time()[iNode], [](int time, const std::pair<int, int>& pr) {return  time < pr.first; });
		if (v.begin() == it)
		{
			return v.size();
		}
		--it;
		return (it->second > m_ct.Time()[iNode]) ? (it - v.begin()) : v.size();
	}
	vector<int> m_vTimeToNode;
	vector<int> m_vNodeToRegion;//各节点所在区域
	const CCutPoint m_ct;
};

class CPointBitConnect :public CCutPoint
{//点双连通区域
public:
	CPointBitConnect(const vector<vector<int>>& vNeiB):CCutPoint(vNeiB)
	{
		
	}
	stack<int> m_staNodes;
	vector<vector<int>> m_vBitConnect;
protected:
	virtual void OnNewRegion(int cur, int next) override {
		m_vBitConnect.emplace_back();
		while (true)
		{
			const int t = m_staNodes.top();
			m_staNodes.pop();
			m_vBitConnect.back().emplace_back(t);
			if (t == next)
			{
				break;
			}
		}
		m_vBitConnect.back().emplace_back(cur);
	};
	virtual void OnBeginDFS(int cur)override {
		m_staNodes.emplace(cur);
	};
};

class Solution {
public:
    long long minimumCost(vector<int>& cost, vector<vector<int>>& roads) {
        m_c = cost.size();
        if (1 == m_c)
        {
            return cost[0];
        }
        auto neiBo = CNeiBo::Two(m_c, roads, false);
        CPointBitConnect pbc(neiBo);
        pbc.Init(neiBo);
        if (1 == pbc.m_vBitConnect.size())
        {
            return *std::min_element(cost.begin(), cost.end());
        }
        long long ans = 0;
        int iMax = 0;
        vector<bool> vCut = pbc.Cut();
        for (auto& v : pbc.m_vBitConnect)
        {
            int iCutCount = 0;
            int iCurMin = INT_MAX;
            for (const auto& iNode : v)
            {
                iCutCount += vCut[iNode];
                if (!vCut[iNode])
                {
                    iCurMin = min(iCurMin, cost[iNode]);
                }
            }
            if (1 == iCutCount)
            {
                iMax = max(iMax, iCurMin);
                ans += iCurMin;
            }
        }
        return ans - iMax;
    }
    int m_c;
};

测试用例


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> cost;
    vector<vector<int>> roads;

    {
        Solution sln;
        cost = { 3,2,1,4 }, roads = { {0,2},{2,3},{3,1} };
        auto res = sln.minimumCost(cost, roads);
        Assert(2, res);
    }
    {
        Solution sln;
        cost = { 1,2,3,4,5,6 }, roads = { {0,1},{0,2},{1,3},{2,3},{1,2},{2,4},{2,5} };
        auto res = sln.minimumCost(cost, roads);
        Assert(6, res);
    }
  
}

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步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++**实现。

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

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

相关文章

Docker容器Docker桌面配置镜像加速

打开Docker Desktop应用程序&#xff0c;点击设置 具体配置如下&#xff1a; {"builder": {"gc": {"defaultKeepStorage": "20GB","enabled": true}},"experimental": false,"features": {"buil…

基于java的足球联赛管理系统(程序+数据库+文档)

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一、研究背景…

【数据库】软件测试之MySQL数据库练习题目

有表如下&#xff1a; Student 学生表 SC 成绩表 Course 课程表 Teacher 老师表 每个学生可以学习多门课程&#xff0c;每一个课程都有得分&#xff0c;每一门课程都有老师来教&#xff0c;一个老师可以教多个学生 1、查询姓‘朱’的学生名单 select * from Student whe…

「CISP题库精讲」CISP题库习题解析精讲20道

前言 本篇主要对CISP教材第九章《计算环境安全》的一些习题进行讲解&#xff0c;包括20道题&#xff0c;这里只是部分习题&#xff0c;针对第九章可能会多写几章的内容&#xff0c;如果我发布的这些习题里面没有你想找的那道题&#xff0c;你也可以直接私信我&#xff0c;我加…

趣学前端 | Taro迁移完成之后,总结了一些踩坑经验

背景 四月份的时候&#xff0c;尝试将老的移动端项目改造成多端。因为老项目使用的React框架&#xff0c;综合考量&#xff0c;保障当前业务开发的进度同时&#xff0c;进行项目迁移&#xff0c;所以最后选择了Taro框架。迁移成本会低一些&#xff0c;上手快一些。 上个月&am…

新书速览|Vue.js 3.x+Element Plus从入门到精通(视频教学版)

详解Vue.jsElement Plus框架各组件的用法&#xff0c;实战网上商城系统和图书借阅系统开发 本书内容 《Vue.js 3.xElement Plus从入门到精通&#xff1a;视频教学版》通过对Vue.js&#xff08;简称Vue&#xff09;的示例和综合案例的介绍与演练&#xff0c;使读者快速掌握Vue.j…

【掌握版本控制:Git 入门与实践指南】配置详解|理解本地仓库结构

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;泥中に咲く—ウォルピスカーター 0:34━━━━━━️&#x1f49f;──────── 4:46 &#x1f504; ◀️ ⏸ ▶…

excel批量数据导入时用poi将数据转化成指定实体工具类

1.实现目标 excel进行批量数据导入时&#xff0c;将批量数据转化成指定的实体集合用于数据操作&#xff0c;实现思路&#xff1a;使用注解将属性与表格中的标题进行同名绑定来赋值。 2.代码实现 2.1 目录截图如下 2.2 代码实现 package poi.constants;/*** description: 用…

力扣--动态规划/回溯算法131.分割回文串

思路分析&#xff1a; 动态规划 (DP)&#xff1a; 使用动态规划数组 dp&#xff0c;其中 dp[i][j] 表示从字符串 s[i] 到 s[j] 是否为回文子串。预处理动态规划数组&#xff1a; 从字符串末尾开始&#xff0c;遍历每个字符组合&#xff0c;判断是否为回文子串&#xff0c;填充…

像SpringBoot一样使用Flask - 2.静态资源访问及模版

一、安装并导入 render_template 功能&#xff1a;渲染/加载模板&#xff0c;一般是html页面 参数&#xff1a;函数的第一个参数是模板的文件名&#xff0c;必填&#xff0c;后面的参数都是键值对&#xff0c;表示模板中变量对应的值&#xff0c;非必填 (不填界面也不会展示成变…

使用express+nginx+pm2+postman实现推送zip包自动更新前端网页

1.nginx配置将80端口代理到项目的3000端口 server {listen 80; #监听的端口server_name localhost; #监听的域名#charset koi8-r;#access_log logs/host.access.log main;location / {#root html;#index index.html index.html;proxy_pass http://127.0.0.1:3000; #转…

Android App冷启动耗时优化

Android应用启动过程 Android应用启动过程&#xff0c;主要包含app::onCreate及执行前的Application阶段及Activity::onCreate执行之后的Activity阶段&#xff0c;以及两个阶段之间的间隙handleMessage阶段和最终页面渲染上屏完成前数据加载阶段四个区间组成。 具体来看&#x…

如何提高内存和cpu使用率呢?-Linux类资源

最近公司项目上线时&#xff0c;红蓝线巡检时&#xff0c;部分服务器因配置高但使用率低而需要降级。 为了避免降级&#xff0c;如何增加内存和CPU使用率&#xff1f; 这可以通过脚本来实现。 当前服务器操作系统版本&#xff1a;-7_5。 连接服务器后&#xff0c;登录账号。 …

深入理解Debug断点调试技巧

在软件开发过程中&#xff0c;调试是一个非常重要的环节&#xff0c;而使用断点是提高调试效率的关键技巧之一。本文将深入探讨如何利用断点进行高效的调试&#xff0c;常用的快捷键以及一些常见的断点调试技巧。 一、调试技巧&#xff1a; 1. 设置断点&#xff1a;在代码中设…

你配赚米吗?韭菜就在繁荣时进场!防诈P的最简单方法,还不告诉你爸妈?——早读(逆天打工人爬取热门微信文章解读)

你听到哪里好&#xff0c;你去了就是韭菜 引言Python 代码第一篇 人民日报 1460万&#xff0c;保住了&#xff01;第二篇 人民日报 夜读 每一次努力&#xff0c;都是幸运的伏笔第三篇 人民日报 来啦 新闻早班车要闻社会政策 结尾 “故天将降大任于是人也&#xff0c;必先苦其心…

CMake 交叉编译

想知道“魔笛手”在这里能发挥什么作用吗&#xff1f;想象一下&#xff0c;把 CMake 当做法力高强的魔笛手&#xff0c;C 的项目则是故事中的那些被魔笛手拯救的孩子。 父母要抚养一个孩子并非易事&#xff0c;营养需要面面俱到&#xff0c;保证身体健康&#xff0c;关心事无巨…

【活动】探索人工智能的“迷惑瞬间”:真实体验与技术挑战

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 标题&#xff1a;探索人工智能的“迷惑瞬间”&#xff1a;真实体验与技术挑战引言…

vulhub中Weblogic 管理控制台未授权远程命令执行漏洞复现(CVE-2020-14882,CVE-2020-14883)

Weblogic是Oracle公司推出的J2EE应用服务器。在2020年10月的更新中&#xff0c;Oracle官方修复了两个长亭科技安全研究员voidfyoo 提交的安全漏洞&#xff0c;分别是CVE-2020-14882和CVE-2020-14883。 CVE-2020-14882允许未授权的用户绕过管理控制台的权限验证访问后台&#x…

英飞凌电源管理PMIC的安全应用

摘要 本篇文档主要用来介绍英飞凌电源管理芯片TLF35584的使用&#xff0c;基于电动助力转向应用来介绍。包含一些安全机制的执行。 TLF35584介绍 TLF35584是英飞凌推出的针对车辆安全应用的电源管理芯片&#xff0c;符合ASIL D安全等级要求&#xff0c;具有高效多电源输出通道&…

[综述笔记]Graph Neural Networks in Network Neuroscience

论文网址&#xff1a;Graph Neural Networks in Network Neuroscience | IEEE Journals & Magazine | IEEE Xplore 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xf…