【线段树】P11414 [EPXLQ2024 fall round] 神奇磁铁|普及+

线段树 C线段树 P11414 [EPXLQ2024 fall round] 神奇磁铁 题目背景 lzy 给了 Cute_QiQi 很多组神奇的磁铁。 注:想拿到快速 AK 变换奖请在代码注释部分写明本题代码

线段树

C++线段树

P11414 [EPXLQ2024 fall round] 神奇磁铁

题目背景

lzy 给了 Cute_QiQi 很多组神奇的磁铁。

注:想拿到快速 AK 变换奖请在代码注释部分写明本题代码正确性证明。

题目描述

一组神奇的磁铁由 2 x 2x 2x 个磁铁排成一排组成,编号 1 , 2 , … , 2 x 1,2,\dots,2x 1,2,,2x,有激活和未激活两种状态。它们与普通的磁铁不同,不会简单地吸在一起。对于一组磁铁,当且仅当存在 y ∈ [ 1 , x ] y \in [1,x] y[1,x],使得不存在两个激活的磁铁满足两者之间的距离为 y y y,整组磁铁才会吸在一起。不同组的磁铁之间不相互影响。

对于编号为 i , j i,j i,j 的两个磁铁,它们之间的距离为 ∣ i − j ∣ |i-j| ij。具体地,当 x = 2 x=2 x=2 时,磁铁组为 { 1 , 2 , 3 , 4 } \{1,2,3,4\} {1,2,3,4}。当激活的磁铁为 { 1 , 2 } \{1,2\} {1,2} 时,整组磁铁可以吸在一起,因为对于 y = 2 y=2 y=2,不存在两个磁铁之间的距离为 2 2 2。而激活的磁铁为 { 1 , 2 , 3 } \{1,2,3\} {1,2,3} 时整组磁铁不能吸在一起。

lzy 给了 Cute_QiQi n n n 组磁铁。现在,Cute_QiQi 希望把这 n n n 组磁铁排成一排作为装饰,同组磁铁堆在一起。未被吸在一起的磁铁不便于摆放,因此,Cute_QiQi 希望所有的磁铁组内的磁铁都吸在一起。在此基础上,Cute_QiQi 希望激活尽可能多的磁铁。

另外,有时 lzy 会给 Cute_QiQi 一些额外的磁铁。

磁铁实在是太多了,以至于 Cute_QiQi 计算不出她最多能激活多少磁铁。因此,她希望你帮她写一个程序,支持下面两种操作:

  • 1 l r x,表示 lzy 给 [ l , r ] [l,r] [l,r] 内所有的磁铁组添加了 2 x 2x 2x 个磁铁;
  • 2 l r,表示 Cute_QiQi 想知道在激活最多磁铁的情况下, [ l , r ] [l,r] [l,r] 内的磁铁组总共有多少个磁铁被激活。

输入格式

输入第一行为 n , q n,q n,q,其中 q q q 表示操作的个数。

第二行为 n n n 个整数 a 1 , a 2 , . … , a n a_1,a_2,.\dots,a_n a1,a2,.,an,表示每个磁铁组中初始的磁铁数量 2 a i 2a_i 2ai

以下 q q q 行,每行一个操作,格式如下:

  • 1 l r x,表示 lzy 给 [ l , r ] [l,r] [l,r] 内所有的磁铁组添加了 2 x 2x 2x 个磁铁;
  • 2 l r,表示 Cute_QiQi 想知道在激活最多磁铁的情况下, [ l , r ] [l,r] [l,r] 内的磁铁组总共有多少个磁铁被激活。

输出格式

对于每个 2 l r 操作,输出一行表示答案。

输入输出样例 #1

输入 #1

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

输出 #1

19
22
28
19

说明/提示

样例解释

初始时对应的激活磁铁的最大数量依次为 1 , 1 , 5 , 6 , 1 , 5 1,1,5,6,1,5 1,1,5,6,1,5

进行修改操作后,对应的激活磁铁最大数量依次为 4 , 4 , 8 , 6 , 1 , 5 4,4,8,6,1,5 4,4,8,6,1,5

可以证明,不可能再激活更多的磁铁。

数据规模与约定

本题采用捆绑测试。

v v v 表示任意时刻,磁铁数最多的磁铁组内磁铁的数量。

Subtask \text{Subtask} Subtask n ≤ n \le n q ≤ q \le q v ≤ v \le v分值
0 0 0 1000 1000 1000 1000 1000 1000 20 20 20 10 10 10
1 1 1 1000 1000 1000 1000 1000 1000 1 0 9 10^9 109 15 15 15
2 2 2 5 × 1 0 5 5 \times 10^5 5×105 5 × 1 0 5 5 \times 10^5 5×105 20 20 20 10 10 10
3 3 3 5 × 1 0 5 5 \times 10^5 5×105 5 × 1 0 5 5 \times 10^5 5×105 5000 5000 5000 25 25 25
4 4 4 5 × 1 0 5 5 \times 10^5 5×105 5 × 1 0 5 5 \times 10^5 5×105 1 0 9 10^9 109 40 40 40

对于所有数据,保证 1 ≤ n , q ≤ 5 × 1 0 5 , 1 ≤ l ≤ r ≤ n , 1 ≤ v , a i ≤ 1 0 9 , − 1 0 9 ≤ x ≤ 1 0 9 1 \le n,q \le 5 \times 10^5, 1 \le l \le r \le n, 1 \le v,a_i \le 10^9, -10^9 \le x \le 10^9 1n,q5×105,1lrn,1v,ai109,109x109

P11414 线段树

性质一:一组2x块磁铁,y=1时,最多能激活x块。1,2一组;3,4一组;5,6一组, ⋯ \cdots 共x组。每组只能激活一个,故激活的磁铁不超过x。取1,3,5 ⋯ \cdots ,能激活x块磁铁。
性质二:类似性质一,一组2x+1块磁铁,y=1时,最多能激活x+1块。
性质三:y > 1,可以拆分成y组。第1组:1 ,y+1,2y+1 ⋯ \cdots ,第i组:i ,y+i,2y+I ⋯ \cdots 。各组的长度递减或相等,第一组的长度和最后一组相等或多一。各组相互无影响,分别用性质一或性质二计算。
性质四:每有2组奇数长度的磁铁,可以多激活1块磁铁。令奇数长度的磁铁组数量为c,则本组磁铁最多可以激活c/2+x。
令某组长度为2x,m = 2x%3,d=z/3。
如果0m, y=d。d组奇数长度。d一定偶数,故本磁铁组最多激活:x + 2x/3/2= x + x/3。
如果1
m,y = d,一组4,其它组3。注意:2x不会是1。d-1组奇数长度。d一定是奇数。即:2x/3是奇数,故x/3是d-1。故本磁铁组最大激活数量为x+x/3。
如果2==m, y=d +1,1组2,其它全部3。d组奇数长度。d即2x/3是偶数,故x/3没有余数。故本磁铁组最大激活数量为x+x/3。
总结:长度为2x的磁体组,最多激活 x + x/3。

线段树

区间更新、静态开点线段树
TSave:i1,l1,i2,l2,i3,l3。int i1记录m为0的节点数量,long long l1记录m为0的节点的d之和;i2,l2记录1m,i3,l3记录2m。
int is[3]代替,i1,i2,i3;int ls代替 l1,l2,l3
TRecord:long long记录增加的量。
更新父节点、查询:直接相加。
更新缓存:直接相加。
ls[i]: ls[i]+(i+update)/3*len
isn[(i+update)%3] = is[i] lsn类似。
最终用isn替换is。

详细讲解及优化

令d1=x/3。
0x%3,则 2d13=2x=d3 → \rightarrow d1=d/2。
1
x%3 ,即2d13+12=2x=d3+2 → \rightarrow d1=d/2。
2 ==x%3,即2d1
3+22=2x=d3+1 → \rightarrow 2d1*3=(d-1)3 → \rightarrow d1 = (d-1)/2。
已知[L,R]的x之和为sum,x%3为1的数量i1,x%3为2的数量i2,则激活的总磁铁数为:
sum + (sum-i1-2
i1)/3
TRecord默认值:0
TSave默认值、查询默认值:i1=1,i2=0,i3=0,sum。初始化是单点更新所有。
查询默认值:is和ls全部是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 = 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 int Read() {
		FileToBuf();
		int x(0), f(0);
		while (!isdigit(*S))
			f |= (*S++ == '-');
		while (isdigit(*S))
			x = (x << 1) + (x << 3) + (*S++ ^ 48);
		return f ? -x : x;
	}
	inline long long ReadLongLong() {
		FileToBuf();
		long long x(0);int f(0);
		while (!isdigit(*S))
			f |= (*S++ == '-');
		while (isdigit(*S))
			x = (x << 1) + (x << 3) + (*S++ ^ 48);
		return f ? -x : x;
	}
private:
	inline void FileToBuf() {
		const int canRead = m_iWritePos - (S - buffer);
		if (canRead >= 100) { return; }
		if (m_bFinish) { return; }
		if (0 != canRead)
		{
			memcpy(buffer, S, canRead);			
		}
		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;
};


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 tuple<int, int, int, 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) {
		get<0>(ans) += get<0>(save);
		get<1>(ans) += get<1>(save);
		get<2>(ans) += get<2>(save);
		get<3>(ans) += get<3>(save);
	}
	inline int Mod3(long long x, long long y) { return ((x + y) % 3 + 3) % 3; };
	virtual void OnUpdate(TSave& save, const int& iSaveLeft, const int& iSaveRight, const TRecord& update) override
	{
		int is[3] = { 0 };
		is[Mod3(0, update)] = get<0>(save);
		is[Mod3(1, update)] = get<1>(save);
		is[Mod3(2, update)] = get<2>(save);
		get<3>(save) += update * (iSaveRight - iSaveLeft + 1);
		get<0>(save) = is[0];
		get<1>(save) = is[1];
		get<2>(save) = is[2];
	}
	virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, const int& iSaveLeft, const int& iSaveRight)  override
	{
		par = left;
		OnQuery(par, r, 0, 0);
	}
	virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord) override
	{
		old += newRecord;
	}
};
class Solution {
public:
	vector<long long> Ans(const vector<int>& a, vector<tuple<int, int, int>>& ope) {
		const int N = a.size();
		CMyLineTree lineTree(N + 1, make_tuple(1, 0, 0, 0), 0);
		for (int i = 1; i <= N; i++) {
			lineTree.Update(i, i, a[i - 1]);
		}
		vector<long long> ans;
		for (const auto& [left, r, x] : ope) {
			if (INT_MIN == x) {
				auto res = lineTree.Query(left, r);
				ans.emplace_back(get<3>(res) + (get<3>(res) - get<1>(res) - 2 * get<2>(res)) / 3);
			}
			else {
				lineTree.Update(left, r, x);
			}
		}
		return ans;
	}
};
int main() {	
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	int n, q,kind;
	cin >> n >> q;
	auto a = Read<int>(n);
	vector<tuple<int, int, int>> ope(q, make_tuple( 0,0,INT_MIN ));
	for (int i = 0; i < q; i++) {
		cin >> kind >> get<0>(ope[i]) >> get<1>(ope[i]);
		if (1 == kind) {
			cin >> get<2>(ope[i]);
		}
	}	
#ifdef _DEBUG		
	/*printf("T=%d,", T);*/
	Out(a, "a=");
	Out(ope, "ope=");
#endif // DEBUG	
	auto res = Solution().Ans(a, ope);
	COutBuff ob;
	for (const auto& i : res) {
		ob.write(i); ob.write('\n');
	}
	return 0;
}

单元测试

vector<int> a;
		vector<tuple<int, int, int>> ope;
		TEST_METHOD(TestMethod11) {
			a = { 1,1,4,5,1,4 }, ope = { {1,6,INT_MIN},{1,3,2},{1,4,INT_MIN},{1,6,INT_MIN},{1,3,-2},{1,6,INT_MIN} };
			auto res = Solution().Ans(a, ope);
			AssertEx({ 19LL,22LL,28LL,19LL }, res);
		}

扩展阅读

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

视频课程

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

测试环境

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

发布者:admin,转转请注明出处:http://www.yc00.com/web/1754763511a5199275.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信