行列操作 分别对应的策略。
列我们可以用线段树直接维护
行 要用前缀和版本思想。。要记录行覆盖的版本时间位置 在那个位置找到那时候列的值。。然后用 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;
}