// 内部资料
考场上的思路
根本没思路……师傅们都说是去年的原题——他们在去年这个时候就已经比我强 倍了 Orz……
思路
师傅的做法太难了,我这种弱智根本听不懂。不过有一种吉司机线段树的做法我倒是听懂了一些。
还是先考虑 Special Instance,若所有询问操作在修改操作之后应该怎么做。我们考虑倒着加入元素,用一棵线段树保存栈底的值。
发现在倒着加入元素时,如果一个元素出现在了栈中,那么它一定比之前的栈底要大,否则他会被之前的栈底弹出。所以如果我们只需要维护栈底的话,我们相当于要做一个区间 checkmax。现在要求栈中元素之和,我们要做的相当于只是多了一个单点加值。
现在考虑所有数据。如何处理修改之前的询问呢?发现,我们在询问时将该位置清零,再继续倒着做就好了。但是如果一个位置有多个询问呢?干脆把所有询问排成一行用线段树维护,而不是维护整个序列。遇到询问时,就把该询问对应的位置清空。做完所有修改后,询问的答案就保存在线段树中。
参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
using LL = long long;
using ULL = unsigned long long;
using std::cin;
using std::cout;
using std::endl;
using INT_PUT = LL;
INT_PUT readIn()
{
INT_PUT a = 0;
bool positive = true;
char ch = getchar();
while (!(std::isdigit(ch) || ch == '-'))
ch = getchar();
if (ch == '-')
{
positive = false;
ch = getchar();
}
while (std::isdigit(ch))
{
(a *= 10) -= ch - '0';
ch = getchar();
}
return positive ? -a : a;
}
void printOut(INT_PUT x)
{
char buffer[20];
int length = 0;
if (x < 0) putchar('-');
else x = -x;
do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
do putchar(buffer[--length]); while (length);
putchar('\n');
}
const int INF = (~(int(1) << (sizeof(int) * 8 - 1))) >> 1;
const int maxn = int(2e5) + 5;
int n, m;
struct Ins
{
int type;
int l, r, x;
void read()
{
type = readIn();
if (type == 1)
{
l = readIn();
r = readIn();
x = readIn();
}
else if (type == 2)
x = readIn();
}
} inss[maxn];
#define RunInstance(x) delete new x
struct brute
{
std::vector<std::vector<int>> stack;
brute()
{
stack.resize(n + 1);
for (int i = 1; i <= m; i++)
{
const Ins &ins = inss[i];
if (ins.type == 1)
{
for (int j = ins.l; j <= ins.r; j++)
{
while (stack[j].size() && stack[j].back() <= ins.x)
stack[j].pop_back();
stack[j].push_back(ins.x);
}
}
else if (ins.type == 2)
{
LL ans = 0;
for (int j = 0; j < stack[ins.x].size(); j++)
ans += stack[ins.x][j];
printOut(ans);
}
}
}
};
struct work
{
struct Query
{
int pos;
int idx;
Query() {}
Query(int pos, int idx) : pos(pos), idx(idx) {}
bool operator<(const Query& b) const
{
return pos < b.pos;
}
};
int N;
Query querys[maxn];
int idx[maxn];
int poss[maxn];
class SegTree
{
static inline int code(int l, int r)
{
return (l + r) | (l != r);
}
struct Node
{
int major;
int minor;
LL sum;
int lazy;
void clear()
{
major = 0;
minor = INF;
sum = 0;
lazy = 0;
}
Node() { clear(); }
} nodes[maxn * 2];
int bound;
int g_L, g_R, g_Pos, g_Val;
void cover(int l, int r, int val, LL val2)
{
Node& t = nodes[code(l, r)];
if (val <= t.major) return; // note
t.major = val;
t.sum += val2;
t.lazy = std::max(t.lazy, val);
}
void pushdown(int l, int r)
{
Node& t = nodes[code(l, r)];
if (t.lazy || t.sum)
{
int mid = (l + r) >> 1;
cover(l, mid, t.lazy, t.sum);
cover(mid + 1, r, t.lazy, t.sum);
t.lazy = 0;
t.sum = 0;
}
}
void update(int l, int r)
{
Node& t = nodes[code(l, r)];
int mid = (l + r) >> 1;
Node& lc = nodes[code(l, mid)];
Node& rc = nodes[code(mid + 1, r)];
if (lc.major == rc.major)
{
t.major = lc.major;
t.minor = std::min(lc.minor, rc.minor);
}
else if (lc.major < rc.major)
{
t.major = lc.major;
t.minor = std::min(lc.minor, rc.major);
}
else if (lc.major > rc.major)
{
t.major = rc.major;
t.minor = std::min(lc.major, rc.minor);
}
}
void checkmax_(int l, int r)
{
Node& t = nodes[code(l, r)];
if (g_Val <= t.major)
return;
if (g_L <= l && r <= g_R && g_Val < t.minor)
{
cover(l, r, g_Val, g_Val);
return;
}
pushdown(l, r);
int mid = (l + r) >> 1;
if (g_L <= mid) checkmax_(l, mid);
if (g_R > mid) checkmax_(mid + 1, r);
update(l, r);
}
void reset_(int l, int r)
{
if (l == r)
{
nodes[code(l, r)].clear();
return;
}
pushdown(l, r);
int mid = (l + r) >> 1;
if (g_Pos <= mid) reset_(l, mid);
else if (g_Pos > mid) reset_(mid + 1, r);
update(l, r);
}
LL query_(int l, int r)
{
if (l == r)
{
return nodes[code(l, r)].sum;
}
pushdown(l, r);
int mid = (l + r) >> 1;
if (g_Pos <= mid) return query_(l, mid);
else if (g_Pos > mid) return query_(mid + 1, r);
}
public:
void SetBound(int b) { bound = b; }
void checkmax(int l, int r, int val)
{
g_L = l;
g_R = r;
g_Val = val;
checkmax_(1, bound);
}
void reset(int pos)
{
g_Pos = pos;
reset_(1, bound);
}
LL query(int pos)
{
g_Pos = pos;
return query_(1, bound);
}
} st;
work() : N(), idx(), poss()
{
for (int i = 1; i <= m; i++)
{
if (inss[i].type == 2)
{
++N;
querys[N] = Query(inss[i].x, i);
}
}
st.SetBound(N);
std::sort(querys + 1, querys + 1 + N);
for (int i = 1; i <= N; i++)
poss[i] = querys[i].pos;
for (int i = 1; i <= N; i++)
idx[querys[i].idx] = i;
for (int i = 1; i <= m; i++)
{
if (inss[i].type == 1)
{
int newL = std::lower_bound(poss + 1, poss + 1 + N, inss[i].l) - poss;
int newR = std::upper_bound(poss + 1, poss + 1 + N, inss[i].r) - poss - 1;
if (newL > newR)
inss[i].type = 0;
else
{
inss[i].l = newL;
inss[i].r = newR;
}
}
}
for (int i = m; i >= 1; i--)
{
if (inss[i].type == 1)
{
st.checkmax(inss[i].l, inss[i].r, inss[i].x);
}
else if (inss[i].type == 2)
{
st.reset(idx[i]);
}
}
for (int i = 1; i <= m; i++)
{
if (inss[i].type == 2)
{
printOut(st.query(idx[i]));
}
}
}
};
void run()
{
n = readIn();
m = readIn();
bool appear = false;
bool t1 = true;
for (int i = 1; i <= m; i++)
{
inss[i].read();
if (inss[i].type == 2)
appear = true;
if (appear && inss[i].type == 1)
t1 = false;
}
RunInstance(work);
}
int main()
{
#ifndef LOCAL
freopen("stack.in", "r", stdin);
freopen("stack.out", "w", stdout);
#endif
run();
return 0;
}
注意吉司机线段树的 cover:所有标记都是在满足修改值小于次小值的情况下打上的,因此修改值一定一直小于次小值,不用与次小值进行判断。但是修改值不一定比最小值大,因此必须做判断。在求和标记(sum
)下传时,也要先判断修改值是否比最小值大,因为当前的求和标记是在修改成修改值之前打上的,如果最小值比修改值要大,那么所有标记都不会下传。
总结
虽然我想过吉司机的看家法宝,但是我想不到倒着做。唉,我太弱啦!