【二分查找 图论】P10050 [CCO2022] Alternating Heights|普及

本文涉及的基础知识点

本博文代码打包下载
C++二分查找
C++图论
C++算法:滑动窗口及双指针总结

[CCO2022] Alternating Heights

题目描述

Troy 计划给 CCO 的学生拍一张合影,他向你寻求帮助。

K K K 个学生,编号从 1 1 1 K K K。Troy 忘记了学生的身高,但他记得没有两个学生的身高相同。

Troy 有一个序列 A 1 , A 2 , … , A N A_{1}, A_{2}, \ldots, A_{N} A1,A2,,AN,表示合影中从左到右的学生顺序。一个学生可能在 A A A 中出现多次。你不确定这张合影会怎么拍,但你不愿意认为 Troy 犯了错误。

Troy 会给你 Q Q Q 个形式为 x , y x,y x,y 的询问,每个询问为「给定学生序列 A x , A x + 1 , … , A y A_{x}, A_{x+1}, \ldots, A_{y} Ax,Ax+1,,Ay,他们的身高能否形成一个交替序列?」更具体地说,我们用 h i h_i hi 表示第 i i i 个学生的身高。如果存在一种身高分配$ h_1, h_2, \ldots, h_K$,使得 h A x > h A x + 1 < h A x + 2 > h A x + 3 < … h A y h_{A_{x}}>h_{A_{x+1}}h_{A_{x+3}}<\ldots h_{A_{y}} hAx>hAx+1<hAx+2>hAx+3<hAy,回答 YES;否则回答 NO

注意,每个查询都是独立的:也就是说,询问 i i i 的身高分配与询问 j j j 的身高分配无关 ( i ≠ j ) (i\neq j) (i=j)

输入格式

第一行包含三个用空格分隔的整数 N , K N, K N,K Q Q Q

第二行包含 N N N 个整数,表示 A 1 , A 2 , … , A N ( 1 ≤ A i ≤ K ) A_{1}, A_{2}, \ldots, A_{N}\left(1 \leq A_{i} \leq K\right) A1,A2,,AN(1AiK)

接下来的 Q Q Q 行,每行包含两个用空格分隔的整数 x x x y ( 1 ≤ x < y ≤ N ) y (1 \leq xy(1x<yN),表示一组查询。

输出格式

输出 Q Q Q 行。第 i i i 行,输出 YES 或者 NO,表示 Troy 的第 i i i 个查询的答案。

样例 #1

样例输入 #1

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

样例输出 #1

NO
YES
NO

提示

样例说明

对于第一个询问,不可能有 h 1 > h 1 h_1>h_1 h1>h1,所以答案是 NO

对于第二个询问, h 1 > h 2 < h 3 > h 1 h_1>h_2h_1 h1>h2<h3>h1 的一种方案是 h 1 = 160   c m , h 2 = 140   c m , h 3 = 180   c m h_1=160 \mathrm{~cm}, h_2=140 \mathrm{~cm}, h_3=180 \mathrm{~cm} h1=160 cm,h2=140 cm,h3=180 cm。另一种方案是 h 1 = 1.55   m , h 2 = 1.473   m , h 3 = 1.81   m h_1=1.55 \mathrm{~m}, h_2=1.473 \mathrm{~m}, h_3=1.81 \mathrm{~m} h1=1.55 m,h2=1.473 m,h3=1.81 m

对于第三个询问,不可能同时有 h 1 > h 2 h_1>h_2 h1>h2 h 1 < h 2 h_1h1<h2

数据范围

对于所有的数据,有 2 ≤ N ≤ 3000 2 \leq N \leq 3000 2N3000 2 ≤ K ≤ N 2 \leq K \leq N 2KN 1 ≤ Q ≤ 1 0 6 1 \leq Q \leq 10^{6} 1Q106

子任务编号 分值 N N N K K K Q Q Q
1 1 1 16 16 16 2 ≤ N ≤ 3000 2 \leq N \leq 3000 2N3000 K = 2 K=2 K=2 1 ≤ Q ≤ 1 0 6 1 \leq Q \leq 10^{6} 1Q106
2 2 2 24 24 24 2 ≤ N ≤ 500 2 \leq N \leq 500 2N500 2 ≤ K ≤ min ⁡ ( N , 5 ) 2 \leq K \leq \min (N, 5) 2Kmin(N,5) 1 ≤ Q ≤ 1 0 6 1 \leq Q \leq 10^{6} 1Q106
3 3 3 28 28 28 2 ≤ N ≤ 3000 2 \leq N \leq 3000 2N3000 2 ≤ K ≤ N 2 \leq K \leq N 2KN 1 ≤ Q ≤ 2000 1 \leq Q \leq 2000 1Q2000
4 4 4 32 32 32 2 ≤ N ≤ 3000 2 \leq N \leq 3000 2N3000 2 ≤ K ≤ N 2 \leq K \leq N 2KN 1 ≤ Q ≤ 1 0 6 1 \leq Q \leq 10^{6} 1Q106

拓扑排序+二分查找

先预处理: ∀ \forall i ,A[i…j]成立的最大j。
拓扑排序实际复杂度:O(n)。
如果暴力处理则复杂度:O(nnn),超时。
可以枚举i,二分j。
如果A[i…j]不成立,则任意A[i…j1]不成立。j1>j。
二分类型:寻找尾端。
参数范围:[i,n-1] 所有编号改成从0开始。

二分代码

子任务四,大部分过不了,在任务三小部分过不了。

#include 
#include 
#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
#include 
#include 
#include
#include

#include 
using namespace std;



template<class T = int>
vector<T> Read(int n,const char* pFormat = "%d") {
	vector<T> ret(n);
	for(int i=0;i<n;i++) {
		scanf(pFormat, &ret[i]);	
	}
	return ret;
}

template<class T = int>
vector<T> Read( const char* pFormat = "%d") {
	int n;
	scanf("%d", &n);
	vector<T> ret;
	T d;
	while (n--) {
		scanf(pFormat, &d);
		ret.emplace_back(d);
	}
	return ret;
}

string ReadChar(int n) {
	string str;
	char ch;
	while (n--) {
		do
		{
			scanf("%c", &ch);
		} while (('\n' == ch));
			str += ch;
	}
	return str;
}
template<class T1,class T2>
void ReadTo(pair<T1, T2>& pr) {
	cin >> pr.first >> pr.second;
}

template<class INDEX_TYPE>
class CBinarySearch
{
public:
	CBinarySearch(INDEX_TYPE iMinIndex, INDEX_TYPE iMaxIndex, INDEX_TYPE tol = 1) :m_iMin(iMinIndex), m_iMax(iMaxIndex), m_iTol(tol) {}
	template<class _Pr>
	INDEX_TYPE FindFrist(_Pr pr)
	{
		auto left = m_iMin - m_iTol;
		auto rightInclue = m_iMax;
		while (rightInclue - left > m_iTol)
		{
			const auto mid = left + (rightInclue - left) / 2;
			if (pr(mid))
			{
				rightInclue = mid;
			}
			else
			{
				left = mid;
			}
		}
		return rightInclue;
	}
	template<class _Pr>
	INDEX_TYPE FindEnd(_Pr pr)
	{
		INDEX_TYPE leftInclude = m_iMin;
		INDEX_TYPE right = m_iMax + m_iTol;
		while (right - leftInclude > m_iTol)
		{
			const auto mid = leftInclude + (right - leftInclude) / 2;
			if (pr(mid))
			{
				leftInclude = mid;
			}
			else
			{
				right = mid;
			}
		}
		return leftInclude;
	}
protected:
	const INDEX_TYPE m_iMin, m_iMax, m_iTol;
};

class CTopSort
{
public:
	template <class T = vector<int> >
	void Init(const vector<T>& vNeiBo, bool bDirect)
	{
		const int iDelOutDeg = bDirect ? 0 : 1;
		m_c = vNeiBo.size();
		m_vBackNeiBo.resize(m_c);
		vector<int> vOutDeg(m_c);
		for (int cur = 0; cur < m_c; cur++)
		{
			vOutDeg[cur] = vNeiBo[cur].size();
			for (const auto& next : vNeiBo[cur])
			{
				m_vBackNeiBo[next].emplace_back(cur);
			}
		}
		vector<bool> m_vHasDo(m_c);
		queue<int> que;
		for (int i = 0; i < m_c; i++)
		{
			if (vOutDeg[i] <= iDelOutDeg)
			{
				m_vHasDo[i] = true;
				if (OnDo(i)) {
					que.emplace(i);
				}
			}
		}

		while (que.size())
		{
			const int cur = que.front();
			que.pop();
			for (const auto& next : m_vBackNeiBo[cur])
			{
				if (m_vHasDo[next])
				{
					continue;
				}
				vOutDeg[next]--;
				if (vOutDeg[next] <= iDelOutDeg)
				{
					m_vHasDo[next] = true;
					if (OnDo(next)) {
						que.emplace(next);
					}
				}
			}
		};
	}
	int m_c;
protected:
	virtual bool OnDo(int cur) = 0;
	vector<vector<int>> m_vBackNeiBo;

};

class CTopSortEx :public CTopSort
{
public:
	virtual bool OnDo(int cur) override
	{
		m_vTopSort.emplace_back(cur);
		return true;
	}
	vector<int> m_vTopSort;
};
class Solution {
		public:
			vector<bool> Ans(const int K,vector<int>&a,vector<pair<int,int>>& que) {
				const int N = a.size();
				for (auto& i : a) {	i--;}
				vector<int> vMax(N, -1);
				int PreEnd = N - 1;
				for (int i = N - 1; i >= 0;i--) {
					for (int j = i + 1; j <= PreEnd; j++) {
						if (a[j] == a[j - 1]) {
							PreEnd = j - 1;
						}
					}
					auto Check = [&](int mid) {
						vector<vector<int>> vBack(K);
						vector<int> vOut(K);
						for (int j = i; j <= mid; j+=2 ) {
							if (j > i ) {								
								vBack[a[j - 1]].emplace_back(a[j ]);
								vOut[a[j]]++;
							}
							if (j + 1 <= mid) {
								vBack[a[j + 1]].emplace_back(a[j ]);
								vOut[a[j]]++;
							}
						}
						queue<int> que;
						for (int i = 0; i < K; i++) {
							if (0 == vOut[i]) { que.emplace(i); }
						}
						int iSortCnt = 0;
						while (que.size()) {
							int cur = que.front();
							que.pop();
							iSortCnt++;
							for (const auto& b : vBack[cur]) {
								vOut[b]--;
								if (0 == vOut[b]) { que.emplace(b); }
							}
						}
						return iSortCnt == K;
					};
					PreEnd = vMax[i] = CBinarySearch<int>(i, PreEnd).FindEnd(Check);
				}
				vector<bool> ans;
				for (auto [x, y] : que) {
					--x, --y;
					ans.emplace_back(vMax[x] >= y);
				}
				return ans;
			}
		};

int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG
	int n,K,m;	
	cin >> n >> K >> m;
	auto a = Read<int>(n);
	vector<pair<int, int>> que;
	while (m--) {
		int t1, t2;
		scanf("%d%d", &t1, &t2);
		que.emplace_back(t1, t2);
	}
#ifdef _DEBUG		
	/*printf("\r\nK=%d;", K);
	Out(a, ",a=");
	Out(que, ",que=");*/
#endif	
	auto res = Solution().Ans(K, a, que);
	for (auto i : res) {
		cout << (i ? "YES" : "NO") << std::endl;
	}
		
	return 0;
}

二分优化

子任务三,只查询2000次。我们只计算查询过的i。性能小幅提升,能够过任务三。子任务四,还是过不了。

class Solution {
		public:
			vector<bool> Ans(const int K,vector<int>&a,vector<pair<int,int>>& que) {
				const int N = a.size();
				for (auto& i : a) {	i--;}
				vector<int> vMax(N, -1);
				auto Cal = [&](int x) {
					auto Check = [&](int mid) {
						vector<vector<int>> vBack(K);
						vector<int> vOut(K);
						for (int j = x; j <= mid; j += 2) {
							if (j > x) {
								vBack[a[j - 1]].emplace_back(a[j]);
								vOut[a[j]]++;
							}
							if (j + 1 <= mid) {
								vBack[a[j + 1]].emplace_back(a[j]);
								vOut[a[j]]++;
							}
						}
						queue<int> que;
						for (int i = 0; i < K; i++) {
							if (0 == vOut[i]) { que.emplace(i); }
						}
						int iSortCnt = 0;
						while (que.size()) {
							int cur = que.front();
							que.pop();
							iSortCnt++;
							for (const auto& b : vBack[cur]) {
								vOut[b]--;
								if (0 == vOut[b]) { que.emplace(b); }
							}
						}
						return iSortCnt == K;
					};
					if (-1 != vMax[x]) { return vMax[x]; }
					return vMax[x] = CBinarySearch<int>(x, N-1).FindEnd(Check);
				};
				vector<bool> ans;
				for (auto [x, y] : que) {
					--x, --y;
					ans.emplace_back(Cal(x) >= y);
				}
				return ans;
			}
		};

单元测试

int K;
		vector<int> a;
		vector<pair<int, int>> que;
	public:
		TEST_METHOD(TestMethod11)
		{	

			K = 3 , a = { 1,1,2,3,1,2 }, que = { {1,2},{2,5},{2,6} };
			auto res = Solution().Ans(K,a,que);
			AssertEx({false,true,false}, res);
		}

滑动窗口

错误想法

如果r >=N, 则vMax[i] = N-1。
否则:
如果Check(i,r)符合 r++
不符合 i++,r不变。如果r 由于每次Check后,i或r都会+1,最大2N次,i或r变成N。
时间复杂度:O(nn)
错误原因:a[i…j]中,a[i+1] < a[i+2];在a[i+1…j]中,a[i+1]>a[i+2]。

正确解法

i分奇偶数,分别处理。

代码

class Solution {
		public:
			vector<bool> Ans(const int K, vector<int>&a,vector<pair<int,int>>& que) {
				const int N = a.size();
				for (auto& i : a) {	i--;}
				vector<int> vMax(N, N-1);				
				auto Windows = [&a,&K,&N,&vMax](int begin) {
					auto Check = [&](int x,int mid) {
						vector<vector<int>> vBack(K);
						vector<int> vOut(K);
						for (int j = x; j <= mid; j += 2) {
							if (j > x) {
								vBack[a[j - 1]].emplace_back(a[j]);
								vOut[a[j]]++;
							}
							if (j + 1 <= mid) {
								vBack[a[j + 1]].emplace_back(a[j]);
								vOut[a[j]]++;
							}
						}
						queue<int> que;
						for (int i = 0; i < K; i++) {
							if (0 == vOut[i]) { que.emplace(i); }
						}
						int iSortCnt = 0;
						while (que.size()) {
							int cur = que.front();
							que.pop();
							iSortCnt++;
							for (const auto& b : vBack[cur]) {
								vOut[b]--;
								if (0 == vOut[b]) { que.emplace(b); }
							}
						}
						return iSortCnt == K;
					};

					for (int left = begin, r = begin; left < N; ) {
						if (r >= N) { break; }
						vMax[left] = r - 1;
						if (Check(left, r)) {
							vMax[left] = r; r++;
						}
						else {
							left +=2 ; if (r < left) { r= left; }
						}
					}
				};
				Windows(0);
				Windows(1);
				vector<bool> ans;
				for (auto [x, y] : que) {
					--x, --y;
					ans.emplace_back(vMax[x] >= y);
				}
				return ans;
			}
		};

扩展阅读

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

视频课程

先学简单的课程,请移步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++**实现。

你可能感兴趣的:(#,洛谷普及,图论,c++,洛谷,二分查找,滑动窗口,身高,学生)