【线段树】P10381 「HOI R1」杂赛选比|普及+
本文涉及知识点
C++线段树
P10381 「HOI R1」杂赛选比
题目背景
你说得对,但是小 ∭ \iiint ∭ 在打 CF 时将 Earn or Unlock 错看成了下面的鬼畜样子,痛失 2h 遗憾离场,希望大家引以为戒。
题目描述
给定一个长度为 n n n 的数组 a a a,初始只有 a 1 a_1 a1 是已被解锁的。现在有一个整数 i i i,初始值为 1 1 1。现在小 ∭ \iiint ∭ 在对这个数组进行一个游戏:
- 如果 a i a_i ai 未被解锁,游戏结束。
- 否则他可以将 a i + 1 ∼ i + a i a_{i+1\sim i+a_i} ai+1∼i+ai 设置成已被解锁的,或是获得 a i a_i ai 个金币(如果 a i = 0 a_i=0 ai=0 则无法解锁任何元素),然后将 i i i 加 1 1 1。
请你求出游戏结束后你能获得的最大金币数量。
输入格式
本题有多组测试数据。
第一行一个整数 T T T,表示测试数据组数。
对于每一组数据,第一行一个正整数 n n n。
接下来一行 n n n 个非负整数 a 1 ∼ n a_{1\sim n} a1∼n。
输出格式
对于每一组数据,一行一个数,表示答案。
输入输出样例 #1
输入 #1
3
2
1 2
5
2 4 5 0 1
4
0 4 4 4
输出 #1
2
9
0
输入输出样例 #2
输入 #2
1
10
1 1 4 5 1 4 1 9 1 9
输出 #2
26
说明/提示
【样例 1 解释】
对于第一组数据,你可以解锁 a 2 a_2 a2,再获得 a 2 a_2 a2 个金币。而对于第三组数据,你无法解锁 a 2 a_2 a2,因此只能获得 0 0 0 个金币。
对于第二组数据,你可以解锁 a 2 , a 3 a_2,a_3 a2,a3,并获得 9 9 9 个金币。
【样例 2 解释】
将第 1 , 2 , 3 , 6 1,2,3,6 1,2,3,6 个位置用于解锁为最优方案。
【数据范围】
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 5 1\le n\le10^5 1≤n≤105, 0 ≤ a i ≤ 1 0 5 0\le a_i\le10^5 0≤ai≤105, T ≤ 5 T\le 5 T≤5。
测试点编号 | n ≤ n\leq n≤ | a i ≤ a_i\leq ai≤ | T = T= T= | 特殊性质 |
---|---|---|---|---|
1 1 1 | 10 10 10 | 0 0 0 | 1 1 1 | / |
2 ∼ 3 2\sim3 2∼3 | 10 10 10 | 5 5 5 | 1 1 1 | / |
4 ∼ 5 4\sim5 4∼5 | 600 600 600 | 600 600 600 | 1 1 1 | / |
6 ∼ 8 6\sim8 6∼8 | 5000 5000 5000 | 5000 5000 5000 | 1 1 1 | / |
9 ∼ 10 9\sim10 9∼10 | 1 0 5 10^5 105 | 5 5 5 | 5 5 5 | / |
11 ∼ 12 11\sim12 11∼12 | 5 × 1 0 4 5\times10^4 5×104 | 1 0 5 10^5 105 | 5 5 5 | a i > n a_i>n ai>n |
13 ∼ 20 13\sim20 13∼20 | 1 0 5 10^5 105 | 1 0 5 10^5 105 | 5 5 5 | / |
线段树
tree[i] 记录[0…i]都已经激活,能够得到的最大分数。
初始值:tree[0]位0,其它LLONG_MIN/2。
i = 0 to N-1
一,x=tree[i…N-1]的最大值。
二,tree[i…N-1] 全部 + a[i]金币。第一步选择金币。
三,teee[min(N-1,i+a[i])] = x。第i步选择激活。
激活和金币不能同时选择。
注意:游戏可以提前结束。
错误线段树:
TSave long long : 金币数,非叶子结束记录最大值。默认值LLONG_MIN/2
TRecord {bool b,long long c}:b为真,max=;b为假,+=。c,设置或增加金币数。{false,0}
查询、更新父节点:取最大值。
更新:b位真,save = update ;为假,save += update。
更新缓存: 新记录b为真:old = newR;否则 old.second += newR.second。
set 和max交替进行,麻烦或无解。
线段树
TSave long long : 金币数,非叶子结束记录最大值。默认值LLONG_MIN/2
TRecord long long:增加金币数。
设置金币数位x1:
查询金币数x2,增加x1-x2。如果x1-x2<=0,无需增加。
代码
核心代码
#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 <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;scanf("%d", &n);vector<T> ret(n);for (int i = 0; i < n; i++) {cin >> ret[i];}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 = 12 * 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;}inline void write(char ch){*m_p++ = ch;}inline void ToFile() {fwrite(puffer, 1, m_p - puffer, stdout);}
private:char puffer[N], * m_p;
};template<int N = 12 * 1'000'000>
class CInBuff
{
public:inline CInBuff() {fread(buffer, 1, N, stdin);}inline int Read() {int x(0), f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);return f ? -x : x;}
private:char buffer[N], * S = buffer;
};template<class TSave, class TRecord >
class CRangUpdateLineTree
{
protected:virtual void OnQuery(TSave& ans, const TSave& save, const int& iSaveLeft, const int& iSaveRight) = 0;virtual void OnUpdate(TSave& save, const int& iSaveLeft, const int& iSaveRight, const TRecord& update) = 0;virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, const int& iSaveLeft, const int& iSaveRight) = 0;virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord) = 0;
};template<class TSave, class TRecord >
class CVectorRangeUpdateLineTree : public CRangUpdateLineTree<TSave, TRecord>
{
public:CVectorRangeUpdateLineTree(int iEleSize, TSave tDefault, TRecord tRecordNull) :m_iEleSize(iEleSize), m_tDefault(tDefault), m_save(iEleSize * 4, tDefault), m_record(iEleSize * 4, tRecordNull) {m_recordNull = tRecordNull;}void Update(int iLeftIndex, int iRightIndex, TRecord value){Update(1, 0, m_iEleSize - 1, iLeftIndex, iRightIndex, value);}TSave Query(int leftIndex, int rightIndex) {return Query(leftIndex, rightIndex, m_tDefault);}TSave Query(int leftIndex, int rightIndex, const TSave& tDefault) {TSave ans = tDefault;Query(ans, 1, 0, m_iEleSize - 1, leftIndex, rightIndex);return ans;}//void Init() {// Init(1, 0, m_iEleSize - 1);//}TSave QueryAll() {return m_save[1];}void swap(CVectorRangeUpdateLineTree<TSave, TRecord>& other) {m_save.swap(other.m_save);m_record.swap(other.m_record);std::swap(m_recordNull, other.m_recordNull);assert(m_iEleSize == other.m_iEleSize);}
protected://void Init(int iNodeNO, int iSaveLeft, int iSaveRight)//{// if (iSaveLeft == iSaveRight) {// this->OnInit(m_save[iNodeNO], iSaveLeft);// return;// }// const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;// Init(iNodeNO * 2, iSaveLeft, mid);// Init(iNodeNO * 2 + 1, mid + 1, iSaveRight);// this->OnUpdateParent(m_save[iNodeNO], m_save[iNodeNO * 2], m_save[iNodeNO * 2 + 1], iSaveLeft, iSaveRight);//}void Query(TSave& ans, int iNodeNO, int iSaveLeft, int iSaveRight, int iQueryLeft, int iQueryRight) {if ((iSaveLeft >= iQueryLeft) && (iSaveRight <= iQueryRight)) {this->OnQuery(ans, m_save[iNodeNO], iSaveLeft, iSaveRight);return;}if (iSaveLeft == iSaveRight) {//没有子节点return;}Fresh(iNodeNO, iSaveLeft, iSaveRight);const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;if (mid >= iQueryLeft) {Query(ans, iNodeNO * 2, iSaveLeft, mid, iQueryLeft, iQueryRight);}if (mid + 1 <= iQueryRight) {Query(ans, iNodeNO * 2 + 1, mid + 1, iSaveRight, iQueryLeft, iQueryRight);}}void Update(int iNode, int iSaveLeft, int iSaveRight, int iOpeLeft, int iOpeRight, TRecord value){if ((iOpeLeft <= iSaveLeft) && (iOpeRight >= iSaveRight)){this->OnUpdate(m_save[iNode], iSaveLeft, iSaveRight, value);this->OnUpdateRecord(m_record[iNode], value);return;}if (iSaveLeft == iSaveRight) {return;//没有子节点}Fresh(iNode, iSaveLeft, iSaveRight);const int iMid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;if (iMid >= iOpeLeft){Update(iNode * 2, iSaveLeft, iMid, iOpeLeft, iOpeRight, value);}if (iMid + 1 <= iOpeRight){Update(iNode * 2 + 1, iMid + 1, iSaveRight, iOpeLeft, iOpeRight, value);}// 如果有后代,至少两个后代this->OnUpdateParent(m_save[iNode], m_save[iNode * 2], m_save[iNode * 2 + 1], iSaveLeft, iSaveRight);}void Fresh(int iNode, int iDataLeft, int iDataRight){if (m_recordNull == m_record[iNode]){return;}const int iMid = iDataLeft + (iDataRight - iDataLeft) / 2;Update(iNode * 2, iDataLeft, iMid, iDataLeft, iMid, m_record[iNode]);Update(iNode * 2 + 1, iMid + 1, iDataRight, iMid + 1, iDataRight, m_record[iNode]);m_record[iNode] = m_recordNull;}vector<TSave> m_save;vector<TRecord> m_record;TRecord m_recordNull;TSave m_tDefault;const int m_iEleSize;
};typedef long long TSave;
typedef long long TRecord;
class CMyLineTree : public CVectorRangeUpdateLineTree<TSave, TRecord>
{
public:using CVectorRangeUpdateLineTree::CVectorRangeUpdateLineTree;
protected:virtual void OnQuery(TSave& ans, const TSave& save, const int& iSaveLeft, const int& iSaveRight) {ans = max(ans, save);}virtual void OnUpdate(TSave& save, const int& iSaveLeft, const int& iSaveRight, const TRecord& update) override{save += update;}virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, const int& iSaveLeft, const int& iSaveRight) override{par = max(left, r);}virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord) override{old += newRecord;}
};
class Solution {
public:long long Ans(const vector<int>& a) {const int N = a.size();CMyLineTree lineTree(N, LLONG_MIN / 2, 0);lineTree.Update(0, 0, -(LLONG_MIN / 2));for (int i = 0; i < N; i++) {const auto x = lineTree.Query(i, N - 1);lineTree.Update(i, N - 1, a[i]);const int iActive = min(N - 1, i + a[i]);const auto iOld = lineTree.Query(iActive, iActive);if (x - iOld <= 0) { continue; }lineTree.Update(iActive, iActive, x - iOld);}const auto ans = lineTree.QueryAll();return ans;}
};int main() {
#ifdef _DEBUGfreopen("a.in", "r", stdin);
#endif // DEBUG int T = 0;cin >> T;for (int i = 0; i < T; i++) {auto a = Read<int>();
#ifdef _DEBUG /*printf("T=%d,", T);*/Out(a, "a=");
#endif // DEBUG auto res = Solution().Ans(a);cout << res << endl;}return 0;
}
代码
vector<int> a;TEST_METHOD(TestMethod11){a = { 1,2 };auto res = Solution().Ans(a);AssertEx(2LL, res);}TEST_METHOD(TestMethod12){a = { 2,4,5,0,1 };auto res = Solution().Ans(a);AssertEx(9LL, res);}TEST_METHOD(TestMethod13){a = { 0,4,4,4 };auto res = Solution().Ans(a);AssertEx(0LL, res);}TEST_METHOD(TestMethod14){a = { 1,1,4,5,1,4,1,9,1,9 };auto res = Solution().Ans(a);AssertEx(26LL, 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++**实现。