技巧
题解
]
STL 巧题合集
vector
存图
只要不存反边,点数小于 $10^7$ 就是短!
离散化
basic_string<int>b;
for(int i=1;i<=n;i++)b+=a[i];
sort(all(b));b.erase(unique(all(b)),b.end());
for(int i=1;i<=n;i++)a[i]=int(lower_bound(all(b))-b.begin())+1;
Old Drive Tree
也就是 odt,珂朵莉树,老司机树,颜色段均摊。
dijkstra & SPFA
感觉非常常见了吧?QwQ
01dj 和 SPFA 也差不多,相信大家都能写。
std::vector<pii>g[N];
int n,m,s,dis[N];
bool vis[N];
std::priority_queue<pii,std::vector<pii>,std::greater<>>q;
dis[s]=0;q.emplace(0,s);
for(;!q.empty();){
int u=q.top().second;q.pop();
if(vis[u])continue;else vis[u]=true;
for(auto[v,t]:g[u])if(dis[v]>dis[u]+t){
dis[v]=dis[u]+t;
if(!vis[v])q.emplace(dis[v],v);
}
}
ABC158D
你有一个字符串 $s$,接下来有 $q$ 次操作。
- 翻转 $s$。
- 在 $s$ 的开头插入字符 $c$。
- 在 $s$ 的结尾插入字符 $c$。
所有操作结束后,输出 $s$。
$|s|\le10^5$,$q\le2\times10^5$
做法
虽然用普通数组都能写但是得写的非常仔细才可以,但是用 deque
可以大大降低码量和调试难度!开 $q_1$ 表示正向字符串,$q_2$ 表示逆向字符串。操作 $1$ 就交换 $q_1$ 和 $q_2$(这是 $\mathcal O(1)$ 的)。操作 $2$ 就在 $q_1$ 头部和 $q_2$ 尾部插入 $c$。操作 $2$ 就在 $q_1$ 尾部和 $q_2$ 头部插入 $c$。
CF1620E
有一个栈,$q$ 次操作。
- 在序列末尾插入 $x$。
- 把序列中所有 $x$ 改成 $y$。
操作结束后输出这个序列。
$q,x,y\le5\times10^5$
做法
不可以真的去改吧?对于每个 $x$ 开个链表 $l$,插入 $x$ 就在 $l_x$ 尾部放入序号,修改就把 $l_x$ 扔到 $l_y$ 里面。复杂度高达 $\mathcal O(q)$,最优。也可以使用 vector
或 basic_string
启发式合并,$\mathcal O(q\log q)$。
LGP1360
有 $n$ 天 $m$ 种技能,每天会给定 $x$,若 $x$ 二进制下第 $y$ 位是 $1$,那么第 $y$ 种技能会提升 $1$。
如果一段时间内,所有技能都提升了相同的值,那这段时间就是均衡时期。问:最长的均衡时期?
$n\le10^5$,$m\le30$
做法
以第一个技能为基准。如果第一个技能进行了提升,就把所有元素都减去一次提升。这样记录的就是其他技能相对于第一个技能的值。
开个 map<vector<int>,int>
表示技能出现时的天数,这样每天计算完后去和 map
里面更新。
QOJ20
给定一个 $n\times m$ 的 $01$ 地图。$0$ 不能走。只能往右或者往下走。给定 $q$ 次询问,问能否从 $(x_1,y_1v)$ 走到 $(x_2,y_2)$。
$n,m\le1000$,$q\le10^6$
做法
分治。把 $[1,n]$ 分成 $[1,\lfloor\frac n2\rfloor]$ 和 $[\lfloor\frac n2\rfloor+1,n]$ 两部分,如果一个询问正好被分成两部分,就把中间那一列取出来,以每个点为起点跑最短路。如果中间某个点能走到 $(x_1,y_1)$ 且能走到 $(x_2,y_2)$ 那这个询问的答案就是 YES
。每次 $m$ 个点要跑 $n\times m$ 的最短路,递归 $\log n$ 次所以总复杂度是 $\mathcal O(nm^2\log n)$。
如果横着分治一次,竖着分治一次,横着分治一次,竖着分治一次,横着分治一次,竖着分治一次……的话,复杂度会变成 $\mathcal O(nm^2)$。证明。
如果直接采用 bitset
去表示中间的一列点(或者横着的一行点),一个点能到达的点就是这个点的右边或上这个点下面的点。判断能否相连就是判断这两个点与起来带不带 $1$。复杂度是 $\mathcal O(\frac{nm^2}{\omega})$。
UVA12538
$n$ 次操作。
- 从 $p$ 位置插入字符串 $s$,记录版本。
- 从 $p$ 位置删除长度为 $c$ 的字符串,记录版本。
- 输出在第 $v$ 个版本中输出从 $p$ 位置开始长度为 $c$ 的字符串。
$n\le5\times10^4$,$\sum|s|\le10^6$,$|\texttt{输出字符串}|\le 20000$,强制在线,每个 $p,s,c,v$ 都要减去之前输出字符串中 c
的数量,保证减去后 $p,s,c,v$ 均合法。
做法
采用 rope
维护 insert
erase
substr
即可,复杂度 $\mathcal O(n\log n)$。(也可可持久化 FHQ Treap)
ABC273E
$q$ 次操作。
- 在序列尾部插入 $x$。
- 删除序列尾部。
- 在第 $x$ 页中写入当前序列。
- 把当前序列变成第 $x$ 页的序列。
每次操作后输出最后一个元素,如果序列为空输出 $-1$。
$1\le q\le5\times10^5$,$x\le10^9$
做法
发现 $x$ 竟然有 $10^9$!采用 map<int,rope<int>>
去维护 push_back
pop_back
operator=
就行了。
LGP1110
给定 $n$ 个队列,初始时第 $i$ 个里面有一个元素 $a_i$,记录这 $n$ 个队列首尾相接的结果为 $b$。
- 在第 $i$ 个队列末尾插入元素 $k$。
- 查询 $b$ 中相邻元素差值的最小值。
- 查询 $b$ 中所有元素差值的最小值。
$n,m\le5\times10^5$,$a_i,k\le5\times10^8$
做法
使用 multiset
可以速通本题。操作 $2$ 在插入时删除新插入点相邻的差,插入插入值和相邻的差就行了。全局最值在插入时与和这个数最近的两个数更新答案。
AGC020C
给定一个长度为 $n$ 的序列,显然这个序列有 $2^n-1$ 个非空子集。问这些子集的中位数是多少?
$n,a_i\le2000$
做法
这些非空子集必然是对称的,也就是说如果一个子集比中位数小,把所有取的数不取,不取的数取,所得到的子集必然比中位数大。因为不考虑空序列所以中位数一定是比 $\lfloor\frac{sum}2\rfloor$ 大的最小的子集。怎么找?bitset
$01$ 背包,f|=f<<x
。复杂度 $\mathcal O(\frac{n^2m}\omega)$。
CF1141F
有一个长度为 $n$ 的序列,需要从里面选出尽可能多的互不相交子串,使得所有子串的和相等并且使子串的数量最多,输出那些子串。
$n\le1500$,$-10^5\le a_i\le10^5$
做法
发现 $n\le1500$ 所以直接弄出所有子串,可以把子串按和扔到 map
里,每次就只要从里面选尽可能多的不相交的就行了。选出尽可能多的不相交的子串可以采取贪心,按照右端点从小到大排序,能选就选就行了。复杂度是 $\mathcal O(n^2\log n^2)=\mathcal O(n^2\log n)$。
LGP3377
有 $n$ 个小根堆,初始时第 $i$ 个里面有一个元素 $a_i$。
- 合并第 $x$ 个数和第 $y$ 个数所在的堆。
- 输出并删除 $x$ 个数所在的堆堆的队顶(若有多个最小数,优先删除先输入的),若已被删除,输出 $-1$。
$n,m\le10^5$,$a_i\le2^{31}-1$
做法
同时维护可并堆和并查集,同时维护数有没有被删掉即可。使用 pbds
可以做到 $\mathcal O(n\log n)$。对于编号关系可以在堆中插入 pair
,在 second
存放编号。
LGP1456
有 $n$ 只猴猴,第 $i$ 只的武力值为 $a_i$,开始时互相不认识。现在发生了 $m$ 场冲突,冲突时猴猴 $x$ 和猴猴 $y$ 会叫上自己所有的朋友(如果 $x$ 和 $y$ 认识冲突就不会发生),从里面选出武力值最高的进行打架。打架后的猴猴武力值减半(向下取整),然后这两群猴猴都会互相认识。对于每次冲突,如果两只猴猴认识对方,输出 $-1$,否则输出打架后他们朋友中武力值最高的猴猴的武力值。
$n,m\le10^5$,$a_i\le32768$
做法
同时维护可并堆和并查集。
LGP6136
有一颗平衡树,起初有 $n$ 个元素为 $a_i$,有 $m$ 种操作。
- 插入一个 $x$。
- 删除一个 $x$。
- 查询 $x$ 的排名。
- 查询排名为 $x$ 的数(如果不存在,则认为是排名小于 $x$ 的最大数)。
- 求 $x$ 的前驱。
- 求 $x$ 的后继。
强制在线,$x$ 异或上次输出的值。保证每次操作合法。
$n\le10^5,m\le10^6$,$a_i,x<2^{30}$
做法
平衡数板子,pbds
维护 insert
erase
order_of_key
find_by_order
lower_bound
即可。由于默认的 pbds
是不可重的,需要元素使用 pair
储存带一个时间戳。
LGP1486
有 $q$ 条指令,工资下界为 $m$,如果有人工资低于下界就离职。
- 在公司中加入工资为 $k$ 的员工,如果 $k<m$ 则直接离开公司。
- 把每位员工工资加上 $k$。
- 把每位员工工资减去 $k$。
- 查询工资第 $k$ 多的员工,如果 $k$ 大于目前员工数目输出 $-1$。
$n\le3\times10^5$,$m\le10^9$
做法
全局加全局减?打个 tag 就行了。注意全局减后有些人会离职,使用 split
操作就可以快速解决。只需要用到 insert
split
find_by_order
就可以在 $\mathcal O(q\log q)$ 解决。
LGP3391
起初有一个长度为 $n$ 的序列,$a_i=i$。现在总共有 $m$ 次操作,每次操作给出 $l,r$ 让你翻转区间 $[l,r]$。
$n,m\le10^5$
做法
开两个 rope
,表示正向和反向,翻转就相当于从中间 split
出一段,交换正向和反向的这一段就可以了。跑的略微有点慢但还是过了。复杂度 $\mathcal O(m\log n)$。
CF1732D2
起初有一个集合,有一个元素 $0$,$q$ 次操作。
- 在集合中插入元素 $x$(保证之前集合里没有 $x$)。
- 在集合中删除元素 $x$(保证之前集合里有 $x$)。
- 求最小非负整数且为 $x$ 的倍数,这个数不能在集合里。
$q\le2\times10^5$,$x\le10^{18}$
做法
大手一挥开三个集合。$s$ 表示题面的那个集合,$ds_k$ 有可能成为 $k$ 的倍数的答案的集合,$fc_k$ 表示 $k$ 的因子的集合。操作 $1$ 直接在 $s$ 中插入 $x$。操作 $2$ 在 $s$ 中删除 $x$,并且给所有 $ds_i,i\in fc_x$ 插入 $x$,使这个数在下次被操作 $3$ 便利时考虑到。操作 $3$ 在 $ds_x$ 中从小到大遍历过去每次取 $ds_x$ 的首个元素(如果为空,就把上次的值加 $x$)。这样如果不考虑 set
的复杂度,复杂度就是调和级数 $\mathcal O(q\log q)$。
CF741D
$n$ 个节点的一棵树,根节点为 $1$,字符集大小为 $m$,每条边上有一个字符。如果一条路径上边的字符连起来是回文串,那就表示这条路径是完美的。问每个点子树中最长的完美路径长度。
$n\le5\times10^5$,$m\le22$
做法
正解是高级算法 dsu on tree,$\mathcal O(nm\log n)$。但是也可以采用另外的做法 unordered_map
启发式合并。重排后回文,相当于至多只有一个字母出现次数为奇数。用状压把每个字符出现次数的奇偶性记下来。如何算某个点子树内的最长路径长度呢?继承每个儿子,然后与跨过这个点的路径取 $\max$。记 $mask_u$ 表示节点 $1$ 到 $u$ 的路径,$mask_v$ 表示节点 $1$ 到 $v$ 的路径,那么 $mask_u\oplus mask_v$ 就是 $u$ 到 $v$ 的路径。对这个点的每个儿子进行启发式合并,取较小集合 $s_{min}$ 的所有点 $(k,v)$($k$ 表示路径,$v$ 表示当路径为 $k$ 时的最长距离),看看 $k\oplus i,\operatorname{pop}(i)\le1\land k\oplus i\in s_{max}$ 能否更新答案即可。如果把 unordered_map
当成 $\mathcal O(1)$ 的话,复杂度也是 $\mathcal O(nm\log n)$。