本文涉及知识点

倍增算法(multiply)、树上倍增、最近公共祖先(LCA)
C++差分数组

P9246 [蓝桥杯 2023 省 B] 砍树

题目描述

给定一棵由 n n n 个结点组成的树以及 m m m 个不重复的无序数对 ( a 1 , b 1 ) , ( a 2 , b 2 ) , … , ( a m , b m ) \left(a_{1},b_{1}\right),\left(a_{2},b_{2}\right),\ldots,\left(a_{m},b_{m}\right) (a1​,b1​),(a2​,b2​),…,(am​,bm​),其中 a i a_{i} ai​ 互不相同, b i b_{i} bi​ 互不相同, a i ≠ b j ( 1 ≤ i , j ≤ m ) a_{i} \neq b_{j}(1 \leq i,j \leq m) ai​=bj​(1≤i,j≤m)。

小明想知道是否能够选择一条树上的边砍断,使得对于每个 ( a i , b i ) \left(a_{i},b_{i}\right) (ai​,bi​) 满足 a i a_{i} ai​ 和 b i b_{i} bi​ 不连通,如果可以则输出应该断掉的边的编号 (编号按输入顺序从 1 1 1 开始),否则输出 -1。

输入格式

输入共 n + m n+m n+m 行,第一行为两个正整数 n , m n,m n,m。

后面 n − 1 n-1 n−1 行,每行两个正整数 x i , y i x_{i},y_{i} xi​,yi​ 表示第 i i i 条边的两个端点。

后面 m m m 行,每行两个正整数 a i , b i a_{i},b_{i} ai​,bi​。

输出格式

一行一个整数,表示答案,如有多个答案,输出编号最大的一个。

输入输出样例 #1

输入 #1

6 2
1 2
2 3
4 3
2 5
6 5
3 6
4 5

输出 #1

4

说明/提示

【样例说明】

断开第 2 2 2 条边后形成两个连通块: { 3 , 4 } , { 1 , 2 , 5 , 6 } \{3,4\},\{1,2,5,6\} {3,4},{1,2,5,6},满足 3 3 3 和 6 6 6 不连通, 4 4 4 和 5 5 5 不连通。

断开第 4 4 4 条边后形成两个连通块: { 1 , 2 , 3 , 4 } , { 5 , 6 } \{1,2,3,4\},\{5,6\} {1,2,3,4},{5,6},同样满足 3 3 3 和 6 6 6 不连通, 4 4 4 和 5 5 5 不连通。

4 4 4 编号更大,因此答案为 4 4 4。

【评测用例规模与约定】

对于 30 % 30 \% 30% 的数据,保证 1 < n ≤ 1 0 3 1<n \leq 10^3 1<n≤103。

对于 100 % 100 \% 100% 的数据,保证 1 < n ≤ 1 0 5 1<n \leq 10^{5} 1<n≤105, 1 ≤ m ≤ n 2 1 \leq m \leq \frac{n}{2} 1≤m≤2n​。

蓝桥杯 2023 省赛 B 组 J 题。

[蓝桥杯 2023 省 B] 砍树 树上差分 LCA

一,以0为根,BFS求出个节点的层次,根节点层次是0。
二,求各节点的父节点par。
三,枚举a、b。令ai和bi的最近公共祖先是ab。则diff[a]++,diff[b]++,diff[ab]-=2。
四,按层次从大到小处理各节点。如果diff[cur] == m,则cur到父节点是合法边之一。diff[par] += diff[cur]。
五,枚举序号最大的可删除边。

代码

核心代码

#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>
#include<array>

#include <bitset>
using namespace std;

template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
	in >> pr.first >> pr.second;
	return in;
}

template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t);
	return in;
}

template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
	return in;
}

template<class T = int>
vector<T> Read() {
	int n;
	cin >> n;
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
	vector<T> ret;
	T tmp;
	while (cin >> tmp) {
		ret.emplace_back(tmp);
		if ('\n' == cin.get()) { break; }
	}
	return ret;
}

template<class T = int>
vector<T> Read(int n) {
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}

template<int N = 1'000'000>
class COutBuff
{
public:
	COutBuff() {
		m_p = puffer;
	}
	template<class T>
	void write(T x) {
		int num[28], sp = 0;
		if (x < 0)
			*m_p++ = '-', x = -x;

		if (!x)
			*m_p++ = 48;

		while (x)
			num[++sp] = x % 10, x /= 10;

		while (sp)
			*m_p++ = num[sp--] + 48;
		AuotToFile();
	}
	void writestr(const char* sz) {
		strcpy(m_p, sz);
		m_p += strlen(sz);
		AuotToFile();
	}
	inline void write(char ch)
	{
		*m_p++ = ch;
		AuotToFile();
	}
	inline void ToFile() {
		fwrite(puffer, 1, m_p - puffer, stdout);
		m_p = puffer;
	}
	~COutBuff() {
		ToFile();
	}
private:
	inline void AuotToFile() {
		if (m_p - puffer > N - 100) {
			ToFile();
		}
	}
	char  puffer[N], * m_p;
};

template<int N = 1'000'000>
class CInBuff
{
public:
	inline CInBuff() {}
	inline CInBuff<N>& operator>>(char& ch) {
		FileToBuf();
		while (('\r' == *S) || ('\n' == *S) || (' ' == *S)) { S++; }//忽略空格和回车
		ch = *S++;
		return *this;
	}
	inline CInBuff<N>& operator>>(int& val) {
		FileToBuf();
		int x(0), f(0);
		while (!isdigit(*S))
			f |= (*S++ == '-');
		while (isdigit(*S))
			x = (x << 1) + (x << 3) + (*S++ ^ 48);
		val = f ? -x : x; S++;//忽略空格换行		
		return *this;
	}
	inline CInBuff& operator>>(long long& val) {
		FileToBuf();
		long long x(0); int f(0);
		while (!isdigit(*S))
			f |= (*S++ == '-');
		while (isdigit(*S))
			x = (x << 1) + (x << 3) + (*S++ ^ 48);
		val = f ? -x : x; S++;//忽略空格换行
		return *this;
	}
	template<class T1, class T2>
	inline CInBuff& operator>>(pair<T1, T2>& val) {
		*this >> val.first >> val.second;
		return *this;
	}
	template<class T1, class T2, class T3>
	inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {
		*this >> get<0>(val) >> get<1>(val) >> get<2>(val);
		return *this;
	}
	template<class T1, class T2, class T3, class T4>
	inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {
		*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);
		return *this;
	}
	template<class T = int>
	inline CInBuff& operator>>(vector<T>& val) {
		int n;
		*this >> n;
		val.resize(n);
		for (int i = 0; i < n; i++) {
			*this >> val[i];
		}
		return *this;
	}
	template<class T = int>
	vector<T> Read(int n) {
		vector<T> ret(n);
		for (int i = 0; i < n; i++) {
			*this >> ret[i];
		}
		return ret;
	}
	template<class T = int>
	vector<T> Read() {
		vector<T> ret;
		*this >> ret;
		return ret;
	}
private:
	inline void FileToBuf() {
		const int canRead = m_iWritePos - (S - buffer);
		if (canRead >= 100) { return; }
		if (m_bFinish) { return; }
		for (int i = 0; i < canRead; i++)
		{
			buffer[i] = S[i];//memcpy出错			
		}
		m_iWritePos = canRead;
		buffer[m_iWritePos] = 0;
		S = buffer;
		int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);
		if (readCnt <= 0) { m_bFinish = true; return; }
		m_iWritePos += readCnt;
		buffer[m_iWritePos] = 0;
		S = buffer;
	}
	int m_iWritePos = 0; bool m_bFinish = false;
	char buffer[N + 10], * S = buffer;
};

class CNeiBo
{
public:
	static vector<vector<int>> Two(int n, const vector<pair<int, int>>& edges, bool bDirect, int iBase = 0)
	{
		vector<vector<int>>  vNeiBo(n);
		for (const auto& [i1, i2] : edges)
		{
			vNeiBo[i1 - iBase].emplace_back(i2 - iBase);
			if (!bDirect)
			{
				vNeiBo[i2 - iBase].emplace_back(i1 - iBase);
			}
		}
		return vNeiBo;
	}
	static vector<vector<int>> Two(int n, const 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<std::pair<int, int>>> Three(int n, const vector<tuple<int, int, int>>& edges, bool bDirect, int iBase = 0)
	{
		vector<vector<std::pair<int, int>>> vNeiBo(n);
		for (const auto& [u, v, w] : edges)
		{
			vNeiBo[u - iBase].emplace_back(v - iBase, w);
			if (!bDirect)
			{
				vNeiBo[v - iBase].emplace_back(u - iBase, w);
			}
		}
		return vNeiBo;
	}
	static vector<vector<int>> Mat(vector<vector<int>>& neiBoMat)
	{
		vector<vector<int>> neiBo(neiBoMat.size());
		for (int i = 0; i < neiBoMat.size(); i++)
		{
			for (int j = i + 1; j < neiBoMat.size(); j++)
			{
				if (neiBoMat[i][j])
				{
					neiBo[i].emplace_back(j);
					neiBo[j].emplace_back(i);
				}
			}
		}
		return neiBo;
	}
};

class CBFSLeve {
public:
	static vector<int> Leve(const vector<vector<int>>& neiBo, vector<int> start) {
		vector<int> leves(neiBo.size(), -1);
		for (const auto& s : start) {
			leves[s] = 0;
		}
		for (int i = 0; i < start.size(); i++) {
			for (const auto& next : neiBo[start[i]]) {
				if (-1 != leves[next]) { continue; }
				leves[next] = leves[start[i]] + 1;
				start.emplace_back(next);
			}
		}
		return leves;
	}
	template<class NextFun>
	static vector<int> Leve(int N, NextFun nextFun, vector<int> start) {
		vector<int> leves(N, -1);
		for (const auto& s : start) {
			leves[s] = 0;
		}
		for (int i = 0; i < start.size(); i++) {
			auto nexts = nextFun(start[i]);
			for (const auto& next : nexts) {
				if (-1 != leves[next]) { continue; }
				leves[next] = leves[start[i]] + 1;
				start.emplace_back(next);
			}
		}
		return leves;
	}
	static vector<vector<int>> LeveNodes(const vector<int>& leves) {
		const int iMaxLeve = *max_element(leves.begin(), leves.end());
		vector<vector<int>> ret(iMaxLeve + 1);
		for (int i = 0; i < leves.size(); i++) {
			ret[leves[i]].emplace_back(i);
		}
		return ret;
	};
	static vector<int> LeveSort(const vector<int>& leves) {
		const int iMaxLeve = *max_element(leves.begin(), leves.end());
		vector<vector<int>> leveNodes(iMaxLeve + 1);
		for (int i = 0; i < leves.size(); i++) {
			leveNodes[leves[i]].emplace_back(i);
		}
		vector<int> ret;
		for (const auto& v : leveNodes) {
			ret.insert(ret.end(), v.begin(), v.end());
		}
		return ret;
	};
};
class CParents
{
public:
	CParents(vector<int>& vParent, long long iMaxDepth)
	{
		int iBitNum = 0;
		for (; iMaxDepth; iBitNum++) {
			const auto mask = 1LL << iBitNum;
			if (mask & iMaxDepth) { iMaxDepth = iMaxDepth ^ mask; }
		}
		const int n = vParent.size();
		m_vParents.assign(iBitNum + 1, vector<int>(n, -1));
		m_vParents[0] = vParent;
		//树上倍增
		for (int i = 1; i < m_vParents.size(); i++)
		{
			for (int j = 0; j < n; j++)
			{
				const int iPre = m_vParents[i - 1][j];
				if (-1 != iPre)
				{
					m_vParents[i][j] = m_vParents[i - 1][iPre];
				}
			}
		}
	}
	int GetParent(int iNode, int iDepth)const
	{
		int iParent = iNode;
		for (int iBit = 0; iBit < m_vParents.size(); iBit++)
		{
			if (-1 == iParent)
			{
				return iParent;
			}
			if (iDepth & (1 << iBit))
			{
				iParent = m_vParents[iBit][iParent];
			}
		}
		return iParent;
	}
	inline int GetBitCnt()const { return m_vParents.size(); };
	inline const int& GetPow2Parent(int iNode, int n)const {
		return m_vParents[n][iNode];
	}
	//在leftNodeExclude的1到rightLeve级祖先中查找符合pr的最近祖先
	template<class _Pr>
	int FindFirst(int leftNodeExclude, int rightLeve, _Pr pr) {
		for (int iBit = GetBitCnt() - 1; iBit >= 0; iBit--) {
			const int iMask = 1 << iBit;
			if (!(iMask & rightLeve)) { continue; }
			if (pr(m_vParents[iBit][leftNodeExclude])) {
				return BFindFirst(leftNodeExclude, iBit, pr);
			}
			leftNodeExclude = m_vParents[iBit][leftNodeExclude];
		}
		return -1;
	}
	//在node的0到rightLeve级祖先中查找符合pr的最远祖先比node高多少层次,这些层次必须存在
	template<class _Pr>
	int FindEnd(int node, int rightLeve, _Pr pr) {
		int leve = 0;
		for (int iBit = GetBitCnt() - 1; iBit >= 0; iBit--) {
			const int iMask = 1 << iBit;
			if (!(iMask & rightLeve)) { continue; }
			if (!pr(m_vParents[iBit][node])) {
				return leve + BFindEnd(node, iBit, pr);
			}
			node = m_vParents[iBit][node];
			leve = leve ^ iMask;
		}
		return leve;
	}
protected:
	//在leftNodeExclude的1到2^pow^级祖先中查找符合pr的最近祖先
	template<class _Pr>
	int BFindFirst(int leftNodeExclude, int pow, _Pr pr) {
		while (pow > 0) {
			const int& mid = m_vParents[pow - 1][leftNodeExclude];
			if (pr(mid)) {
			}
			else {
				leftNodeExclude = mid;
			}
			pow--;
		}
		return m_vParents[0][leftNodeExclude];
	}
	//在node的[0,2^pow^-1]级祖先中寻找符合的最后一个
	template<class _Pr>
	int BFindEnd(int node, int pow, _Pr pr) {
		int leve = 0;
		while (pow > 0) {
			pow--;
			const int& mid = m_vParents[pow][node];
			if (pr(mid)) {
				node = mid;
				leve = leve ^ (1 << pow);
			}
			else {

			}

		}
		return leve;
	}
	vector<vector<int>> m_vParents;
};

class C2Parents : public CParents
{
public:
	C2Parents(vector<int>& vParent, const vector<int>& vDepth) :m_vDepth(vDepth)
		, CParents(vParent, *std::max_element(vDepth.begin(), vDepth.end()))
	{
	}
	int GetPublicParent(int iNode1, int iNode2)const
	{
		int leve0 = m_vDepth[iNode1];
		int leve1 = m_vDepth[iNode2];
		if (leve0 < leve1)
		{
			iNode2 = GetParent(iNode2, leve1 - leve0);
			leve1 = leve0;
		}
		else
		{
			iNode1 = GetParent(iNode1, leve0 - leve1);
			leve0 = leve1;
		}
		if (iNode1 == iNode2) { return iNode1; }
		for (int iBit = GetBitCnt() - 1; iBit >= 0; iBit--) {
			const int iMask = 1 << iBit;
			if (iMask & leve0) {
				const int i1 = GetPow2Parent(iNode1, iBit);
				const int i2 = GetPow2Parent(iNode2, iBit);
				if (i1 == i2) {
					while (iBit > 0) {
						const int i3 = GetPow2Parent(iNode1, iBit - 1);
						const int i4 = GetPow2Parent(iNode2, iBit - 1);
						if (i3 != i4) {
							iNode1 = i3; iNode2 = i4;
						}
						iBit--;
					}
					return GetPow2Parent(iNode1, 0);
				}
				else {
					iNode1 = i1; iNode2 = i2; leve0 -= iMask;
				}
			}
		}
		return iNode1;
	}
protected:

	vector<vector<int>> m_vParents;
	const vector<int> m_vDepth;
};

class Solution {
public:
	int Ans(const int N, vector<pair<int, int>>& edge, vector<pair<int, int>>& ab) {
		const int M = ab.size();
		for (auto& [u, v] : edge) {
			u--, v--;
		}
		for (auto& [a, b] : ab) { a--; b--; }
		auto neiBo = CNeiBo::Two(N, edge, false);
		auto leves = CBFSLeve::Leve(neiBo, { 0 });
		auto leveNodes = CBFSLeve::LeveNodes(leves);
		vector<int> par(N, -1);
		for (auto& [u, v] : edge) {
			if (leves[u] > leves[v]) {
				swap(u, v);
			}
			par[v] = u;
		}
		C2Parents pars(par, leves);
		vector<int> diff(N);
		for (const auto& [a, b] : ab) {
			const int ab = pars.GetPublicParent(a, b);
			diff[a]++; diff[b]++; diff[ab] -= 2;
		}
		vector<bool> canDel(N);
		for (auto it = leveNodes.rbegin(); it != leveNodes.rend(); ++it) {
			for (const auto& cur : *it) {
				canDel[cur] = M == diff[cur];
				const int iPar = par[cur];
				if (-1 == iPar) { continue; }
				diff[iPar] += diff[cur];
			}
		}
		for (int i = edge.size(); i >= 1; i--) {
			if (canDel[edge[i - 1].second]) { return i; }
		}
		return -1;
	}
};


int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	//ios::sync_with_stdio(0); cin.tie(nullptr);
	CInBuff<> in; COutBuff<10'000'000> ob;	
	int N, M;
	in >> N >> M;
	auto edge = in.Read <pair<int,int> > (N-1);
	auto ab = in.Read<pair<int,int>>(M);
#ifdef _DEBUG		
		printf("N=%d", N);
		Out(edge, ",edge=");
		Out(ab, ",ab=");
		//Out(B, "B=");
		//Out(que, "que=");
		//Out(B, "B=");
#endif // DEBUG	
		auto res = Solution().Ans(N,edge,ab);
		cout << res;
	return 0;
}

单元测试

int N;
		vector<pair<int, int>> edge;
		vector<pair<int,int>>ab;
		TEST_METHOD(TestMethod01)
		{
			N = 6, edge = { {1,2},{2,3},{4,3},{2,5},{6,5} }, ab = { {3,6},{4,5} };
			auto res = Solution().Ans(N, edge, ab);
			AssertEx(4, res);
		}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

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

更多推荐