F - Operations on a Matrix 版本前缀和 离线问题 维护数据技巧

行列操作 分别对应的策略。
列我们可以用线段树直接维护
行 要用前缀和版本思想。。要记录行覆盖的版本时间位置 在那个位置找到那时候列的值。。然后用 sum[r]-sum[l]这样的方式 来获取之间列的变化量
答案就是 最近一次行的覆盖值+那个列的前缀和。
实现的关键在于记录sum[l]的那个版本的版本位置。

这题如果行也是修改的 而不是覆盖的话。这种策略就不行了。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll __int128_t
#define ar array<int, 2>
#define arr array<int, 4>
int  n, m, k, inf = 1LL << 61, mod = 998244353;// 1e9+7;
const int N = 5e5 + 50;
// 线段树部分 我就给不贴了。这边只保留核心逻辑
arr a[N];
vector<int>mp[N];//在当前版本位置 需要扣除 前缀sum[l]的所有 的询问编号
int p[N];//p记录行的最近一次覆盖的版本时间
int  ans[N];
void solve() {
	cin >> n >> m >> k;
	for (int i = 1; i <= k; ++i) {
		int o, x, y, z;
		cin >> o >> x >> y;
		if (o == 1) {
			cin >> z;
		} else if (o == 2) {
			p[x] = i;
		} else {
			int j = p[x];
			ans[i] = a[j][2];
			mp[j].push_back(i);
		}
		a[i] = {o, x, y, z};
	}

	build(1, 1, m);
	for (int i = 1; i <= k; ++i) {
		auto[o, l, r, z] = a[i];
		if (o == 1) {
			erfen(1, l, r, z);
		} else if (o == 2) {
			for (int j : mp[i]) {
				int c = a[j][2];
				ans[j] -= query(1, c, c);
			}
		} else
			cout << query(1, r, r) + ans[i] << '\n';

	}

};


带一堆口水话的注释版本。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll __int128_t
#define ar array<int, 2>
#define arr array<int, 4>
int  n, m, k, inf = 1LL << 61, mod = 998244353;// 1e9+7;
const int N = 5e5 + 50;

#define lc rt << 1
#define rc rt << 1 | 1
//int a[N + 2];
struct tree {
	int l, r;
	long long add, sum;
} t[4 * N + 2];

void build(int rt, int l, int r) {
	t[rt] = {l, r, 0, 0};//这样写能用build多次重置映射。
	if (l == r) {
		//t[rt].sum = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(lc, l, mid);
	build(rc, mid + 1, r);
	// t[rt].sum = t[lc].sum + t[rc].sum;
}
void ff(int rt, int tag) {
	t[rt].add += tag;
	t[rt].sum += (long long)tag * (t[rt].r - t[rt].l + 1);
}
void push_down(int rt) {
	int tag = t[rt].add;
	if (!tag)
		return;
	ff(lc, tag);
	ff(rc, tag);
	t[rt].add = 0;
}
void erfen(int rt, int l, int r, int c) {
	if (l <= t[rt].l && t[rt].r <= r)

	{
		ff(rt, c);
		return;
	}
	push_down(rt);
	int mid = (t[rt].l + t[rt].r) >> 1;
	if (l <= mid)
		erfen(lc, l, r, c);
	if (mid < r)
		erfen(rc, l, r, c);
	t[rt].sum = t[lc].sum + t[rc].sum;
}

int query(int rt, int l, int r) {
	if (l <= t[rt].l && t[rt].r <= r)
		return t[rt].sum;
	push_down(rt);
	int mid = (t[rt].l + t[rt].r) >> 1;
	int ans = 0;
	if (l <= mid)
		ans += query(lc, l, r);
	if (r > mid)
		ans += query(rc, l, r);
	return ans;
}
int p[N], ans[N];
vector<int>mp[N];
arr a[N];//这边下标让他从1 开始。和p[i]=0区分开。//a[0]会被访问到。!!!! 不能开在solve里面。
void solve() {
	cin >> n >> m >> k;
	for (int i = 1; i <= k; ++i) {
		int o, l, r, x;
		cin >> o >> l >> r;
		if (o == 1) {
			cin >> x;
		} else if (o == 2) {
			p[l] = i;//这边我们存i 不存x 因为存i可以拿到版本位置 又可以通过a[i]来拿到x。
		} else {
			ans[i] = a[p[l]][2];//这时候要马上拿这个p[l]。因为这个p[l]是在线的实时变化的 。这时候不拿 后期会被覆盖掉
			mp[p[l]].push_back(i);//就是前缀和sum[j]-sum[i]里面的 sum[i]的位置。。下面在线的时候要查 这里先记录下来那些i需要
		}
		a[i] = {o, l, r, x};//都固定成四个变量,好写。但是有些是无效的。自己要知道有效的是哪几个。
	}
	// cout << k << '\n';
	build(1, 1, m);
	for (int i = 1; i <= k; ++i) {
		auto[o, l, r, x] = a[i];
		if (o == 1) {
			erfen(1, l, r, x);
		} else if (o == 2) {
			// 这时候 l就是i, r就是x
			for (int j : mp[i]) {
				ans[j] -= query(1, a[j][2], a[j][2]);
			}
		} else {
			cout << query(1, r, r) + ans[i] << '\n';//这里的+号。细节。
		}
	}
};

// 这种 很不好调。。。不debug的话。。
//这题可以用线段树   。离线的。。行的话 我们可以用前缀和的思想。。那到那个版本的。。
// 我们要记录当前行 操作的下标位置。。我们可以通过他 来找到前缀和的l的位置。。。 这是这题的关键🔥

//1 要记录行最近的一次覆盖的版本
//2 对于每个询问 要把询问序列加入到 对应的行的版本的查询序列里面。。




signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout << fixed << setprecision(15);
#ifdef DEBUG
	freopen("../1.in", "r", stdin);
#endif
	//init_f();
	//init();
	//expr();
	// int T; cin >> T; while(T--)
	solve();
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值