ABC253F Operations on a Matrix 题解

题目要求我们维护一个矩阵,并支持对其在纵向的区间加、横向的单点修改和对于某一个元素当前值的查询。

直接维护矩阵可以让我们以最劣 O ( n 2 q ) O(n^2q) O(n2q) 的时间复杂度黯然离场。

考虑这些修改对于当前格子的影响。
或者进一步说,我们需要求出来对于我们被询问的格子受到了哪些修改的影响,并将这些修改的影响叠加起来给出一个值。

因为横向是单点推平,我们在进行了一次推平之后,其之前的所有纵向上的区间加对其的影响就可以忽略了。
所以我们每次单点推平的时候记录一个时间戳,每一次询问到该点的时候把当前该点处区间加的影响减去时间戳对应的时间该点处区间加的影响,再加上推平的那个值,就是当前点的值了。

题外话:
场上以为横向的是区间推平,于是就写了一个线段树上去。
后面发现不是区间推平,于是把线段树的区间推平改成了单点推平。
然后发现不需要区间查询。
那我写个线段树有个啥子用处?
果断改成了数组。

然后就是可以随时拿出历史版本的线段树了,也就是可持久化线段树。
我们之间学的可持久化线段树是单点修改的,不需要懒标记,也没有pushdown什么的。十分简单。
这里我们需要支持区间修改,也就需要支持懒标记了。
但此时我们的标记不支持pushdown,因为很可能把后面的标记下放到过去的某个状态中。即使我们pushdown的时候为两个子节点新开两个节点记录状态,最终也会发现任何形式的pushdown都不是正确的。

正确的方式是标记永久化。

我实现的方式是,每一次修改的时候只累加懒标记,查询的时候因为是单点查询,在回溯的时候直接把一路上的懒标记加起来就可以了。

代码如下:

int segadd(int p, int l, int r, int k)
{
	int q = ++idx;
	tr[q] = tr[p];
	if(tr[p].l >= l && tr[p].r <= r)
	{
		tr[q].tag += k;
		return q;
	}
	int mid = (tr[p].l + tr[p].r) >> 1;
	if(l <= mid)tr[q].ls = segadd(tr[p].ls, l, r, k);
	if(r > mid)tr[q].rs = segadd(tr[p].rs, l, r, k);
	return q;
}
int segsum(int p, int pos)
{
	if(tr[p].l == tr[p].r)return tr[p].tag;
	int mid = (tr[p].l + tr[p].r) >> 1;
	if(pos <= mid)return segsum(tr[p].ls, pos) + tr[p].tag;
	if(pos > mid)return segsum(tr[p].rs, pos) + tr[p].tag;
}

然后就是主席树的其他基本操作了。

总体的代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200010;
int n, m;
struct ChmTree
{
	struct Node
	{
		int l, r;
		int ls, rs;
		int tag;
	};
	Node tr[N * 64];
	int root[N];
	int idx = 0;
	int build(int l, int r)
	{
		int p = ++idx;
		tr[p] = { l,r,0,0,0 };
		if(l == r)
		{
			return p;
		}
		int mid = (l + r) >> 1;
		tr[p].ls = build(l, mid);
		tr[p].rs = build(mid + 1, r);
		return p;
	}
	int segadd(int p, int l, int r, int k)
	{
		int q = ++idx;
		tr[q] = tr[p];
		if(tr[p].l >= l && tr[p].r <= r)
		{
			tr[q].tag += k;
			return q;
		}
		int mid = (tr[p].l + tr[p].r) >> 1;
		if(l <= mid)tr[q].ls = segadd(tr[p].ls, l, r, k);
		if(r > mid)tr[q].rs = segadd(tr[p].rs, l, r, k);
		return q;
	}
	int segsum(int p, int pos)
	{
		if(tr[p].l == tr[p].r)return tr[p].tag;
		int mid = (tr[p].l + tr[p].r) >> 1;
		if(pos <= mid)return segsum(tr[p].ls, pos) + tr[p].tag;
		if(pos > mid)return segsum(tr[p].rs, pos) + tr[p].tag;
	}
};
ChmTree col;
pair<int, int> row[N];
signed main()
{
	int q;
	scanf("%lld%lld%lld", &n, &m, &q);
	col.root[0] = col.build(1, m);
	for(int i = 1; i <= q; i++)
	{
		int op, l, r, k;
		scanf("%lld%lld%lld", &op, &l, &r);
		if(op == 1)
		{
			scanf("%lld", &k);
			col.root[i] = col.segadd(col.root[i - 1], l, r, k);
		}
		else if(op == 2)
		{
			row[l] = make_pair(r, i);
			col.root[i] = col.root[i - 1];
		}
		else
		{
			col.root[i] = col.root[i - 1];
			int sum = col.segsum(col.root[i], r);
			sum -= col.segsum(col.root[row[l].second], r);
			sum += row[l].first;
			printf("%lld\n", sum);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
国赛题目一般来源于科学与工程技术、人文与社会科学等领域经过适当简化加工的实际问题。参赛者不需要预先掌握深入的专门知识,只需要学过高等学校的数学基础课程。对于解题思路,可以根据题目要求进行以下步骤来完成建模和求解: 1. 理解题目:仔细阅读题目,理解问题的背景和要求。注意提取关键信息,明确问题的目标和约束条件。 2. 建立数学模型:根据题目的描述和要求,将实际问题转化为数学模型。可以使用已知的数学理论、公式和方法,结合问题的特点进行建模。根据问题类型,可以将问题归类为分类问题、优化问题、预测问题或评价问题。 3. 求解模型:根据建立的数学模型,使用适当的数学工具和方法进行求解。可能需要进行数值计算、优化算法或统计分析等操作,以得到问题的解答。 4. 分析和检验结果:对求解结果进行分析和检验,验证其合理性和正确性。可以通过对比实际数据或进行敏感性分析来评估模型的准确性和可靠性。 5. 模型的改进:根据对结果的分析和检验,对模型进行改进。可以尝试不同的假设或调整参数,以提高模型的性能和适应性。 综上所述,解题思路主要包括理解题目、建立数学模型、求解模型、分析和检验结果以及模型的改进。具体的解题思路会根据不同的题目和问题类型有所差异,建议参赛者根据具体题目要求和自身的数学知识经验,灵活运用数学方法和工具,进行问题的建模和求解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值