杂题小记(2023.02.27)
目录更好的阅读体验戳此进入
LG-P3865 【模板】ST 表
别问我为什么又写一遍 ST 表模板,问就是太久没写了重写一遍试试。。。
LG-P3293 [SCOI2016]美味
题面
给定序列 $ a_n \(,\) m $ 次询问每次给定 $ b, x, l, r $,求区间 $ [l, r] $ 最大的 $ b \oplus (a_i + x) $。
Solution
考虑对于一般的无偏移的此类异或最大值可以用 01Trie 实现,对于此题我们首先考虑 01Trie 上每个节点的本质,即对于一个表示了第 $ i + 1 $ 位的值的节点,令 $ cur $ 表示到这个点的前缀的表示的值,其左右儿子是否存在则分别对应着区间 $ [cur, cur + 2^i - 1] $ 与 $ [cur + 2^i, cur + 2^{i + 1} - 1] $ 是否存在着值。再看一遍我们上述的这句话,发现我们将在 01Trie 上的查询操作变为了区间查询的操作,此时可以容易地进行偏移 $ x $,即在区间 $ [l, r] $ 中查询上述值域 $ -x $ 后是否有值即可,也就是模拟 01Trie 实现。对于具体的实现考虑用动态开点可持久化值域线段树即可,复杂度为 $ O(n \log^2 n) $。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define LIM (110000)
template < typename T = int >
inline T read(void);
int N, M;
class SegTree{
private:
struct Node{Node *ls, *rs; int cnt;};
#define MID ((gl + gr) >> 1)
public:
Node* root[210000];
Node* Build(int gl = 0, int gr = LIM){
if(gl == gr)return new Node{npt, npt, 0};
return new Node{Build(gl, MID), Build(MID + 1, gr), 0};
}
SegTree(void){root[0] = Build();}
Node* Create(int val, Node* lstp, int gl = 0, int gr = LIM){
Node* p = lstp ? new Node{lstp->ls, lstp->rs, lstp->cnt + 1} : new Node{npt, npt, 1};
if(gl != gr){
if(val <= MID)p->ls = Create(val, p->ls, gl, MID);
else p->rs = Create(val, p->rs, MID + 1, gr);
}return p;
}
int Query(int l, int r, Node* &p, int gl = 0, int gr = LIM){
if(!p)p = new Node{npt, npt, 0};
if(l <= gl && gr <= r)return p->cnt;
if(gr < l || gl > r)return 0;
return Query(l, r, p->ls, gl, MID) + Query(l, r, p->rs, MID + 1, gr);
}
}st;
int main(){
N = read(), M = read();
for(int i = 1; i <= N; ++i)st.root[i] = st.Create(read(), st.root[i - 1]);
while(M--){
int base = read(), exc = read(), l = read(), r = read();
int cur(0);
for(int i = 20; i >= 0; --i){
auto [rngl, rngr] = base & (1 << i) ? pair{cur - exc, cur + (1 << i) - 1 - exc} : pair{cur + (1 << i) - exc, cur + (1 << (i + 1)) - 1 - exc};
int cnt = rngl < 0 && rngr < 0 ? 0 : st.Query(rngl, rngr, st.root[r]) - st.Query(rngl, rngr, st.root[l - 1]);
cur += (bool(base & (1 << i)) ^ (bool)cnt) << i;
}printf("%d\n", cur ^ base);
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P5490 【模板】扫描线
题面
求二维平面平行坐标轴矩形面积并。
Solution
大概是突然发现以前居然都只是口糊的,没正经写过扫描线。。。
按照????里的思路写了一下,写成的是带 Pushdown
的标准线段树,然后调来调去改了半天,然后才发现这玩意应该写成类似标记永久化的形式,对于满的区间直接打标记就完事了,然后因为查询有且只有总的查询,所以只维护一个朴素的 Pushup
即可,也不需要用到永久化的合并标记等。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define d(p) (vdata.at(p - 1))
template < typename T = int >
inline T read(void);
int N;
ll ans(0);
struct Line{int l, r; int h; int val;};
basic_string < Line > lines;
basic_string < int > vdata;
class SegTree{
private:
int len[210000 << 2], lz[210000 << 2];
#define LS (p << 1)
#define RS (LS | 1)
#define MID ((gl + gr) >> 1)
public:
void Pushup(int p, int gl, int gr){
if(!lz[p])len[p] = gl == gr ? 0 : len[LS] + len[RS];
else len[p] = d(gr + 1) - d(gl);
}
void Modify(int l, int r, int val, int p = 1, int gl = 1, int gr = N - 1){
if(l <= gl && gr <= r)return lz[p] += val, Pushup(p, gl, gr);
if(l <= MID)Modify(l, r, val, LS, gl, MID);
if(r >= MID + 1)Modify(l, r, val, RS, MID + 1, gr);
Pushup(p, gl, gr);
}
int Query(void){return len[1];}
}st;
int main(){
N = read();
for(int i = 1; i <= N; ++i){
int x1 = read(), y1 = read(), x2 = read(), y2 = read();
vdata += {x1, x2};
lines += {Line{x1, x2, y1, 1}, Line{x1, x2, y2, -1}};
}N = lines.size();
sort(vdata.begin(), vdata.end());
vdata.erase(unique(vdata.begin(), vdata.end()), vdata.end());
for(auto &line : lines){
line.l = distance(vdata.begin(), next(lower_bound(vdata.begin(), vdata.end(), line.l)));
line.r = distance(vdata.begin(), next(lower_bound(vdata.begin(), vdata.end(), line.r))) - 1;
}sort(lines.begin(), lines.end(), [](const Line &a, const Line &b)->bool{return a.h < b.h;});
for(auto it = lines.begin(); it != lines.end(); advance(it, 1)){
int curh = it->h;
st.Modify(it->l, it->r, it->val);
while(next(it) != lines.end() && next(it)->h == curh)
advance(it, 1), st.Modify(it->l, it->r, it->val);
if(next(it) != lines.end())ans += (ll)st.Query() * (next(it)->h - curh);
}printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P1856 [IOI1998] [USACO5.5] 矩形周长Picture
题面
求二维平面内平行于坐标轴矩形的并的周长。
Solution
本来感觉就是一道很白给的题,但是写起来发现还是会有一些细节的,两处小错误调了好久。
首先是可以有一个更容易的写法的,不需要维护一大堆东西,直接考虑对于一般的扫描线,有贡献的情况仅为原来不存在边的地方有边了或者原来有边的地方删掉了,于是我们可以直接考虑维护本次查询的有边的长度,与上次查询的长度做差,绝对值即为贡献。此时不难发现问题,对于一般的写法我们会将相同高度的合在一起处理,比如面积并,对于本题我们考虑一个矩形的上边与另一个的下边重合,就会发现少计算贡献了,对于这种也不难想到不合并相同高度的,分别处理,同时对于 $ h $ 相同的我们还需要让 $ 1 $ 在 $ -1 $ 之前,即如果 $ -1 $ 更先就会导致两者之间的重合部分被重复计算。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define d(p) (vdata.at(p - 1))
template < typename T = int >
inline T read(void);
int N;
int rN;
ll ans(0);
struct Line{int l, r; int h; int val;};
basic_string < Line > lines;
basic_string < int > vdata;
tuple < int, int, int, int > vals[5100];
class SegTree{
private:
int len[11000 << 2], lz[11000 << 2];
#define LS (p << 1)
#define RS (LS | 1)
#define MID ((gl + gr) >> 1)
public:
void Pushup(int p, int gl, int gr){
if(lz[p])len[p] = d(gr + 1) - d(gl);
else len[p] = gl == gr ? 0 : len[LS] + len[RS];
}
void Modify(int l, int r, int val, int p = 1, int gl = 1, int gr = N - 1){
if(l <= gl && gr <= r)return lz[p] += val, Pushup(p, gl, gr);
if(l <= MID)Modify(l, r, val, LS, gl, MID);
if(r >= MID + 1)Modify(l, r, val, RS, MID + 1, gr);
Pushup(p, gl, gr);
}
int Query(void){return len[1];}
}st, st2;
int main(){
rN = read();
for(int i = 1; i <= rN; ++i){
int x1 = read(), y1 = read(), x2 = read(), y2 = read();
vals[i] = {x1, y1, x2, y2};
vdata += {x1, x2};
lines += {Line{x1, x2, y1, 1}, Line{x1, x2, y2, -1}};
}N = lines.size();
sort(vdata.begin(), vdata.end());
vdata.erase(unique(vdata.begin(), vdata.end()), vdata.end());
for(auto &line : lines){
line.l = distance(vdata.begin(), next(lower_bound(vdata.begin(), vdata.end(), line.l)));
line.r = distance(vdata.begin(), next(lower_bound(vdata.begin(), vdata.end(), line.r))) - 1;
}sort(lines.begin(), lines.end(), [](const Line &a, const Line &b)->bool{return a.h == b.h ? a.val > b.val : a.h < b.h;});
int lstlen(0);
for(auto it = lines.begin(); it != lines.end(); advance(it, 1)){
st.Modify(it->l, it->r, it->val);
int curlen = st.Query();
ans += abs(curlen - lstlen);
lstlen = curlen;
}vdata.clear(), lines.clear();
for(int i = 1; i <= rN; ++i){
auto [x1, y1, x2, y2] = vals[i];
vdata += {y1, y2},
lines += {Line{y1, y2, x1, 1}, Line{y1, y2, x2, -1}};
}sort(vdata.begin(), vdata.end());
vdata.erase(unique(vdata.begin(), vdata.end()), vdata.end());
for(auto &line : lines){
line.l = distance(vdata.begin(), next(lower_bound(vdata.begin(), vdata.end(), line.l)));
line.r = distance(vdata.begin(), next(lower_bound(vdata.begin(), vdata.end(), line.r))) - 1;
}sort(lines.begin(), lines.end(), [](const Line &a, const Line &b)->bool{return a.h == b.h ? a.val > b.val : a.h < b.h;});
lstlen = 0;
for(auto it = lines.begin(); it != lines.end(); advance(it, 1)){
st2.Modify(it->l, it->r, it->val);
int curlen = st2.Query();
ans += abs(curlen - lstlen);
lstlen = curlen;
}printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P1972 [SDOI2009] HH的项链
题面
给定序列,多组询问求区间 $ [l, r] $ 有多少种数。
Solution
莫队复杂度无法通过,存在一个简单但很高妙的做法,考虑将询问离线按右端点排序,从 $ 1 $ 到 $ n $ 移动右端点,同时用 BIT 维护在当前右端点之前每个元素最后一次出现的位置,在 BIT 中将这个位置 $ +1 $,对于右端点为 $ r $ 的每个 $ l $ 直接查询即可。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template < typename T = int >
inline T read(void);
int N, M;
int A[1100000];
int lst[1100000];
int ans[1100000];
class BIT{
private:
int tr[1100000];
public:
int lowbit(int x){return x & -x;}
void Modify(int x, int v){while(x <= N)tr[x] += v, x += lowbit(x);}
int Query(int x){int ret(0); while(x)ret += tr[x], x -= lowbit(x); return ret;}
}bit;
struct Query{int l, idx;};
basic_string < Query > r[1100000];
int main(){
N = read();
for(int i = 1; i <= N; ++i)A[i] = read();
M = read();
for(int i = 1; i <= M; ++i){
int l = read();
r[read()] += Query{l, i};
}
for(int i = 1; i <= N; ++i){
if(lst[A[i]])bit.Modify(lst[A[i]], -1);
bit.Modify(i, 1), lst[A[i]] = i;
for(auto q : r[i])ans[q.idx] = bit.Query(i) - bit.Query(q.l - 1);
}
for(int i = 1; i <= M; ++i)printf("%d\n", ans[i]);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3582 [POI2015] KIN
题面
给定序列 $ A_n $ 和 $ W_m \(,\) A_i \in [1, m] $,你可以任选序列 $ A $ 的一个区间,定义其价值为区间内存在且仅存在一次的 $ A_i $ 对应的 $ W_{A_i} $ 之和,最大化价值,求最大值。
Solution
类似上一题,用线段树维护,每个点维护到当前 $ r $ 的后缀和,对于首次的值 $ +w $,上次的进行 $ -2w $ 使得其变为 $ -w $,上上次的 $ +w $ 变为 $ 0 $ 即可,容易实现。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template < typename T = int >
inline T read(void);
int N, M;
int A[1100000];
int llst[1100000];
int lst[1100000];
ll w[1100000];
ll ans(0);
class SegTree{
private:
ll mx[1100000 << 2], lz[1100000 << 2];
#define LS (p << 1)
#define RS (LS | 1)
#define MID ((gl + gr) >> 1)
public:
void Pushup(int p){
mx[p] = max(mx[LS], mx[RS]);
}
void Pushdown(int p){
if(!lz[p])return;
lz[LS] += lz[p], lz[RS] += lz[p];
mx[LS] += lz[p], mx[RS] += lz[p];
lz[p] = 0;
}
void Modify(int l, int r, int val, int p = 1, int gl = 1, int gr = N){
if(l <= gl && gr <= r)return mx[p] += val, lz[p] += val, void();
Pushdown(p);
if(l <= MID)Modify(l, r, val, LS, gl, MID);
if(r >= MID + 1)Modify(l, r, val, RS, MID + 1, gr);
Pushup(p);
}
ll QueryMax(int l, int r, int p = 1, int gl = 1, int gr = N){
if(l <= gl && gr <= r)return mx[p];
if(l > gr || gl > r)return 0;
Pushdown(p);
return max(QueryMax(l, r, LS, gl, MID), QueryMax(l, r, RS, MID + 1, gr));
}
}st;
int main(){
N = read(), M = read();
for(int i = 1; i <= N; ++i)A[i] = read();
for(int i = 1; i <= M; ++i)w[i] = read();
for(int i = 1; i <= N; ++i){
if(llst[A[i]])st.Modify(1, llst[A[i]], w[A[i]]);
if(lst[A[i]])st.Modify(1, lst[A[i]], -2 * w[A[i]]);
st.Modify(1, i, w[A[i]]);
llst[A[i]] = lst[A[i]], lst[A[i]] = i;
ans = max(ans, st.QueryMax(1, i));
}printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P4551 最长异或路径
题面
给定带边权树,求树上异或和最大的路径。
Solution
考虑将树上根到每一个叶子节点的异或和记录并插进 01Trie,然后枚举刚才记录的每一个值在 01Trie 上查异或最大值然后取 $ \max $ 即可,正确性显然。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define LIM (31)
template < typename T = int >
inline T read(void);
class Trie{
private:
struct Node{Node* son[2];};
Node* root;
public:
Trie(void){root = new Node{{npt, npt}};}
void Insert(int val){
auto cur = root;
for(int i = LIM; i >= 0; --i){
bool bit = val & (1 << i);
if(!cur->son[bit])cur->son[bit] = new Node{{npt, npt}};
cur = cur->son[bit];
}
}
int QueryMax(int val){
auto cur = root; int ans(0);
for(int i = LIM; i >= 0; --i){
bool bit = val & (1 << i);
ans |= (((bool)cur->son[bit ^ 1] ^ bit) << i);
cur = cur->son[bit ^ 1] ? cur->son[bit ^ 1] : cur->son[bit];
}return ans ^ val;
}
}trie;
int N;
int ans(0);
basic_string < int > vals;
struct Edge{
Edge* nxt;
int to;
int val;
OPNEW;
}ed[210000];
ROPNEW;
Edge* head[110000];
void dfs(int p = 1, int fa = 0, int cur = 0){
trie.Insert(cur), vals += cur;
for(auto i = head[p]; i; i = i->nxt)
if(SON != fa)dfs(SON, p, cur ^ i->val);
}
int main(){
N = read();
for(int i = 1; i <= N - 1; ++i){
int s = read(), t = read(), v = read();
head[s] = new Edge{head[s], t, v};
head[t] = new Edge{head[t], s, v};
}dfs();
for(auto v : vals)ans = max(ans, trie.QueryMax(v));
printf("%d\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
UPD
update-2023_02_27 初稿
标签:Node,27,return,int,2023.02,ret,杂题,void,define From: https://www.cnblogs.com/tsawke/p/17180251.html