test2
2024.1.30
P10114
注意数据范围的诈骗。
本题中 1≤n≤2×106 ,似乎 O(n2) 的暴力不可以通过,问题就出在似乎上。
此题限制了 ∑di≤5×107 ,也就是说不同个数的 di 最多只有约 5×107 个。可以统计不同的数的贡献和相同的数的贡献,具体可以看代码实现。
update:2024.4.17:哦原来这就叫平衡规划。
update:2024.4.23:哦原来这是平衡规划中最简单的一类题。
P10118
有趣。
主要掌握了一种套路 x+y=2(xandy)+xxory
P3916
建反边。
找每个点能遍历到的点编号最大值,朴素做法对每个点 dfs TLE。于是可以对原图建反边再按编号从大到小搜每个没被遍历过的点。
P1197
逆序思考,离线。
此题中要是一个一个分裂复杂度是 O(n2) ,我对分裂操作不知所措,所以只能暴力。
然而如果反过来思考每次操作是合并那么就可以用熟悉的并查集来维护了。
代码 细节很多,调了一小时。/kk
P4185
离线。
和上题类似,对询问的 值从大到小排序就不用删边只用加边,这样并查集就能做了。
一遍过(
P4462
稍微有点费脑的基础莫队。
题意:求有多少个 x 和 y 满足 ax⨁ax+1⨁…⨁ay=k, 询问的 k 值不变。
首先根据莫队分块,其次对整数数列求异或前缀和,接着开桶记录当前区间内每个异或前缀和出现的次数。
为什么要维护这个?
当增加某个点时:
设增加的这个点是 x ,那么 ax 的值即为原数组的 a1⨁a2⨁a3⨁...⨁ax−1⨁ax,我们记录现在的 visax 的个数 。
设另一个数 y 满足 ,如何记录这一组 (x,y) 呢?
由于之前已经记录了 ay 即原数组的 a1⨁a2⨁a3⨁...⨁ay−1⨁ay,接下来分类讨论一下。
当 x<y 时,现在的 ax−1⨁ay 就是以前的
a1⨁a2⨁a3⨁...⨁ay−1⨁ay⨁a1⨁a2⨁a3⨁...⨁ax−1
=a1⨁a1⨁a2⨁a2⨁a3⨁a3⨁...⨁ax−1⨁ax−1⨁ax⨁ax+1⨁ax+2⨁...⨁ay−1⨁ay
=ax⨁ax+1⨁ax+2⨁...⨁ay−1⨁ay
当 x≥y 时,同理可得上面的式子。
所以如果现在的 ax−1⨁ay=k,那么找到了一组合法解 (x,y)。
所以询问区间 l 到 r,只需要从 l−1 到 r 找有多少个 ax−1⨁ay=k 就行了。
CF1070C
题意:在 n 天内,有 m 种物品。其中第 i 种物品有 ci 个,且将在 [li,ri] 这几天出现,价格为 pi。给定一个 k,每天要租借一天价格最小的 k 个物品(如不够就能租多少租多少)。求这 n 天要花的钱。
首先扫描线降掉天数那一维,接着用值域线段树维护每个单价所对应的个数。
接着在每条线段最左端在单价 pi 处增加 ci ,最右端加一处在单价 pi 处减少 ci。
此时的问题就变成了每天求解前 k 小的值所花的费用。
线段树询问:
如果本节点的 cnt 个数等于 k,那么加上本节点的贡献。
如果左子树的 cnt 个数大于等于 k ,那么递归左子树。
如果左子树的 cnt 个数小于 k,那么递归右子树,并且需加上左子树的贡献。
如果本节点为叶子节点,那么加上本节点的贡献与要求的贡献的较小值。
- 哪儿都得开
long long
P4556
线段树合并板子。
树链问题,转化为树上差分。
注意:如果不可持久化,每一次合并完一个点后需要立即计算答案,因为以后的合并可能会影响这个答案。
P4197
离线,线段树合并。
这题把我线段树合并板子 hack 了。
线段树合并代码:
void Merge(int &k,int u){
if(!k||!u){
k+=u;
return ;
}
if(t[k].l==t[k].r){//以前没加这个特判,如果两边都有叶子节点就寄了
t[k].sum+=t[u].sum;
return;
}
Merge(lc,t[u].lson);
Merge(rc,t[u].rson);
Pushup(k);
}
CF526F
题意:
给定一个 n×n 的棋盘,其中有 n 个棋子,每行每列恰好有一个棋子。
对于所有的 1≤k≤n,求有多少个 k×k 的子棋盘中恰好有 k 个棋子。
n≤3×105。
做这题的时候思路很混乱。
二维问题,考虑降维。
注意到 n×n 的棋盘中只有 n 个棋子,且行列互不相同。
套路1:
行列互不相同的矩阵信息可以转化为排列:(i,ai)
我们可以对第一维排序,这样第一维就是连续的。
问题转化为:计数任意连续合法子排列。
合法条件:
在 l 到 r 行中让矩阵上下滑动,直到矩阵中存在 r−l+1 个棋子,如图:绿色表示合法矩阵。
因为行已经连续了(以行为关键字排序),问题转化为在行定了后,列上棋子连续的方案数。
套路2:
值域连续段的判断条件为 极差大小 = 区间长度 。
即 Max[l,r]−Min[l,r]+1=r−l+1
这个式子中有下标 l,r 两维限制,显然可以用扫描线降掉 r 这一维,因此式子变为:
Sufmax[l]−Sufmin[l]+1=r−l+1
移项。
Sufmax[l]−Sufmin[l]+l=r
所以问题又转换成如何快速查询合法左端点的个数。
注意到后缀最值是单调的,所以可以用一个单调栈来维护它,所有弹出的地方均会发生最值的变化,再用线段树去修改这一段区间内的信息。
线段树维护 Sufmax[l]−Sufmin[l]+l 。
查询合法左端点就需要快速统计有多少区间内的数恰好等于 r。
这玩意儿怎么维护?
由于 r 一直在变,线段树 Pushup 的时候要开一个桶去预处理,那么时间空间复杂度都会爆炸。所以结论是快速统计区间内有多少数恰好等于一个数,这是没法做的!
凌门一脚了。
虽然线段树无法快速支持查询区间内有多少数好等于一个数,但是线段树可以维护最值出现的次数。
对于本题,注意到十分重要的一点:
Sufmax[l]−Sufmin[l]≥r−l。
而我们正好就要求有多少个 Sufmax[l]−Sufmin[l]=r−l,所以线段树真正应该维护的是 Sufmax[l]−Sufmin[l] 和最小值出现的个数,最后在扫描线的时候看看 t[1].mn 等不等于 0,如果等于直接加上 t[1].sum 就行。
柳暗花明又一村。
P1501
LCT 维护树链,洛谷线段树二加强版。
几个需要注意的地方
-
Split 操作后直接对根节点进行操作,懒标记直接打,不用考虑后效性,因为以后的 Access-Splay 中会下传懒标记。
-
维护树链上的区间权值加:
-
修改时:
t[y].sum=(t[y].sum+c*t[y].sz)%mod;
-
下传懒标记时:
t[t[x].ch[0]].sum=(t[t[x].ch[0]].sum+t[x].add*t[t[x].ch[0]].sz)%mod;
t[t[x].ch[1]].sum=(t[t[x].ch[1]].sum+t[x].add*t[t[x].ch[1]].sz)%mod;
-
注意!!!这里只改了 sum,但实际操作时也要修改 lazy_add、val 等。
-
-
取模需谨慎
- 在乘法取模时,要写成这样的形式:
(x*=y)%=mod;
- 具体可看此帖。
- 在乘法取模时,要写成这样的形式:
CF609F
线段树二分。
首先离散化青蛙所在的位置开一棵线段树,线段树拿来记录每只青蛙最远能吃到蚊子的地方。
接着模拟蚊子,如果这只蚊子不能被吃到,那就把它扔进 multiset 或者一棵离散化蚊子所在的位置的线段树里。
我菜,写不来 multiset+ 重载运算符,所以只好写该死的又臭又长的线段树了。
如果能被吃到,那就在线段树上二分一下找到最左边能吃到这只蚊子的青蛙,接着在维护青蛙的线段树上加上这只蚊子的贡献,再在维护蚊子的线段树上找有没有以前的蚊子会被这只青蛙吃掉,如下图:
注意到紫色区域已经没有新的蚊子了,所以本轮循环可以终止了。
这题代码调了 2 天。
P4115
Qtree4 毒瘤树剖解法。
没关系,虽然不是人能写的,但我还是硬着头皮写了一小时,调了两天最终过掉了此题。
题意:求带修点颜色下最远白点距离。
路径信息,考虑在 LCA 处求解,通过拼接快速求解所有可能路径。
由于带修,所以需要将所有节点作为 LCA 时的答案存放在一个全局堆里维护。
但是修改一个节点会导致他的多个祖先结点的答案发生变化,这样的话空间时间复杂度都是 O(n2)。
考虑优化。
显然以每个点作为 LCA 处统计最大值会储存过多的信息,于是可以在一条重链上统计以重链上每个点作为 LCA 时的最大值。
设所有可能路径 (x,y) 满足 LCA(x,y)=z 时,此时有两种情况:
- 若路径 (x,y) 不经过 z 所在重链,则 x,y 到 z 的的祖先链分别位于 z 的两个不同轻子树上,如图:
- 若若路径 (x,y) 经过 z 所在重链,那么 x,y 到 z 的祖先链分别位于 z 的一棵重子树和 z 的一棵轻子树上,如图:
综上,树链全局最优解可以以重链为单位整体维护存储,即链分治。
再来看到重链上子树内部最远白点的拼接的情况:
由于是拼接操作,很自然地想到用线段树维护,例如最大子段和问题。
设 z=LCA(x,y) ,(x,y) 相当于所有白点拼接的情况。
- 维护答案时的操作
- 若路径 (x,y) 不经过 z 所在重链:在线段树上区间 [z,z] 这个叶节点上维护它轻子树上的最远与次远白点距离。注意:这两个点不能在同一颗轻子树!
- 若路径 (x,y) 经过 z 所在重链,那就只需在线段树上维护两个在重链上的点的轻子树内最远白点的距离再拼接起来即可。
- 修改操作的影响:
- 线段树上的信息仅受子树内白点信息影响,考虑修改一个点 x。
- 这个点会在线段树上影响自己,每条 x 的祖先链上的答案,祖先链链头的父亲的轻子树最大次大值。所以可以依次查找 x 的祖先重链,用链头处的后缀最远白点更新链头父亲的信息。
- 首先需要更新一个节点子树中最远+次远白点信息。
- 带修,所以需要维护轻子树半群信息,因此不能仅仅维护最大值和次大值,还需用数据结构去维护最值。
- 注意:虽然这里说是轻子树半群信息,然而一个节点子树中最远+次远白点信息其实还包含他本身,所以也需维护它自己。
- 可以使用手写堆(由于要删除值)或 STL 里的 multiset 来维护一个节点子树中最远+次远白点信息。
- 其次,维护每条 x 的祖先链上的答案,这个在线段树 Pushup 时已经处理好了。
- 查询操作:
- 需要动态查询所有重链信息的最大值,因此将每条重链的最大值扔进全局最值数据结构里查询。
- 同样的,这里也用手写堆或 multiset。
最后是时间复杂度:显然重剖有一只 log,线段树搭配重剖有一只 log,堆或者 multiset 搭配重剖有一只 log。
所以总时间复杂度为 O(qlog2n)
但是由于我懒,用的 STL,所以常数稍微有点大。
这题代码调了 3 天。
HDU4747
题意:求一个序列中所有区间的 mex 之和。
扫描线好题,考虑定一求一。
首先考虑定 r,再快速统计所以以 l 开始的后缀 mex 和。
但是发现如果加入一个数到序列中,我们并不能 O(1) 把这个序列的 mex 求出来。
正难则反,如果把序列中一个数删除就可以 O(1) 求出新序列的 mex 的值了。
由于有这个性质,所以我们可以从后往前扫描线。统计以一个点作为 r 时的所有 mex 之和,再删除这个点做出的贡献。
考虑这个点做出了什么贡献:
显然点 i 的删除只会将绿色部分的后缀 mex 设为 ai。
所以我们需要完成的操作有:
- 区间赋值
- 区间求和
- 区间查询在 i 前第一个大于 ai 的值的下标。
所以使用线段树。
注意:线段树二分时如果区间内没有数能满足条件,直接 return
。
void Ask(int k,int l,int x){
if(t[k].r<l||ans)return;//ans 初始化时一开始设为 0
if(t[k].l>=l){
if(t[k].mex<=x)return;//对于本题就是这里
if(t[k].l==t[k].r){
ans=t[k].l;
return;
}
Pushdown(k);
if(t[rc].mex>x)Ask(rc,l,x);
else Ask(lc,l,x);
return;
}
Pushdown(k);
Ask(rc,l,x);
Ask(lc,l,x);
}
P7647
注意到所有操作向左向右移动都不会改变每 m 个数字中的相对顺序,将这每 m 个数字称为这 m 个链表。
update 2024.4.24:其实这 m 个链表就是同一剩余类,但懒得改了。
这只会改变这 m 个链表中链头是谁,如图:
所以维护 head 表示现在的第一个位置是哪个链表里的,再维护每一个链表里的相对位移即可。
-
一操作只会影响 head,直接将 head 加 x 取模即可。
-
二操作会影响每一个链表中的相对顺序。
- 所有组都会整体向左移动一个数。
- 还有一些会向左多移动一。
- 差分维护即可
-
取模的时候需要加上很大倍的模数再除以模数,如本题
-
int y=q[i].x;if(q[i].x<0)y=(114514*n+y)%n;
P9809
set 的 lower_bound 这么用,以此题为例:
int mn=inf;
for(int i=0;i<maxm;i+=x){
if(q.lower_bound(i)==q.end())break;
mn=min(mn,*q.lower_bound(i)-i);
}
P4135
题意:区间加区间求第 k 小值。
序列问题,思考数据结构。
若没有区间加,考虑主席树。
带修,考虑分块求解。
二分答案区间第 k 小值。
问题转化为判定二分的答案是否为第 k 小值。
只需查询区间中比他小的数与 k 的关系即可。
若比他小的数大于 k,所以这个数肯定不是第 k 小的值,让 r=mid−1 即可。
反之 l=mid 即可。
BSOJ7714
题意:
给定一个长度为 n 的正整数序列 ai。
将 1,2,...,n 划分成两个非空集合 、、S、T,使得 gcd(∏i∈Sai,∏i∈Tai)=1
求划分方案数,对 109+7 取模。
分析:
显然可以把不互素的数捆在一起,找连通块个数为 k,答案即为 2k−2。
如何把他们捆一起呢?
注意到素因子个数是 O(logn) 的,所以可以把数和他的质因子连边,这样每次连边就是 logn 的。
我考试的时候全部推出来了,但我写成了 O(nn) 的复杂度,喜提 70 分。
错误实现代码(我用的 DFS)
void Add(int x){//O(sqrt(n))
vst[x]=0;qwq[x]=1;
if(!vis[x])return;
int xx=x;
for(int i=1;i<=tot;i++){
if(x==1||xx==prime[i])return;
if(x%prime[i]==0){
add(xx,prime[i]);
add(prime[i],xx);
vst[prime[i]]=0;
}
while(x%prime[i]==0)x/=prime[i];
}
}
正确实现代码:在素数筛的时候预处理即可。
void Sieve(){
for(int i=2;i<maxm;i++){
if(vis[i])continue;
prime[++tot]=i;
for(int j=2*i;j<maxm;j+=i){
vis[j]=1;
E[j].push_back(i);//预处理质因子
}
}
}
void Add(int x){//O(log n)
qwq[x]=1;
if(!vis[x])return;
for(int i=0;i<E[x].size();i++)add(x,E[x][i]);
}
班上同学全写的并查集,我要证明,DFS,是正确的复杂度!!!11
CF883B
二维偏序。
题意:
给定 n 个点, m 条有向边,点权值上限 k。使得所有点的点权在 1 k 中,且已给定一部分点权,请构造一种方案满足:
- 对于所有由 x 指向y 的边,x 的点权大于 y 。
- 对于i=1,2,...,k , 至少有一个点的点权为 i。
若无解,输出 −1。
第二个要求简单地说就是每个权值都得有一个点是这个权值。
分析:
题目切入点显然是第一个要求。
- 考虑何时无解?
- 如果有向图出现了环,那么无解。
- 若可能有解,有向图一定是 DAG。
注意到每个未确定的点肯定是有权值区间的。
可以正图拓扑 DP 求权值范围右端点,反图拓扑 DP 求权值范围左端点。
小提示 1:写代码的时候可能会遇到权值已经确定的点,其实没有什么关系的。
DP 方程:
令 lx 为第 x 个点权值范围左端点,rx 为第 x 个点权值范围右端点。
令 y 是 i 的出边节点,则有:
ry=min(ry,ry−1)
lx=max(lx,rx+1)
若有一个点 x 使得 lx>rx,那么无解。
接着需贪心构造使该图满足第二个要求。
可以按照权值贪心从小到大依次构造点的权值使该图满足条件。
如果有区间的权值范围左端点小于等于正在决策的权值,那么将他加入候选队列。
- 队列中若有右端点等于决策权值,则将他们的权值都设为该权值。
- 队列中若有右端点大于决策权值,那么将右端点最小的节点设为该权值一定最优。
- 队列中不可能有右端点小于决策权值,因为若是有这样的点他们已经在前面的决策权值选择过了。
我们发现所谓“队列”需维护最小右端点,所以使用小根堆或 set 维护即可。
P2093
强大题目。
求第 k 大的点可以往小根堆里加入 k 个 −inf,如果元素比堆顶的值大,那么弹出堆顶,加入该元素。
由于是小根堆,所以堆顶动态维护第 k 大值,所以最后输出堆顶即可。
如果元素不足 k 个,堆顶显然是 −inf。
一种便于理解的方法是小根堆里面始终只有 k 个元素所以堆顶是第 k 大值。
CF613D
题意:树上删最少点使关键点不连通(不能删关键点)。
牛逼 DP。
观察题目,发现 ∑ki≤105。
考虑虚树。
此时虚树里只有关键点即非关键点,考虑 DP。
设 fx 为以 x 为根的子树中,是否有关键点与 x 联通,(如果x为关键点显然则 fx 为 1)。
也可以这么理解:fx 表示需不需要再删一个点使祖先关键点与以 x 子树的关键点不联通。fx=1 表示需要。
对于点 x,令 sum 为它的子树中 fy=1 的数量。
- x 为关键点:
- 必须使它与 fx=1 的子树断开,删掉 x 与虚树儿子节点间的一个点,所以贡献为 sum,此时 fx=1。
- x 不为关键点:
- 若 sum>1:
- 删掉 x 使子树关键点不连通,fx=0,贡献为 1。
- 若 sum=1:
- fx=1。
- 若 sum=0:
- fx=0。
- 若 sum>1:
关键代码:
void Aux_Dfs(int x,int fath){//虚树上 DFS
int sum=0;
for(int i=0;i<E[x].size();i++){
int y=E[x][i];
if(y==fath)continue;
Aux_Dfs(y,x);
if(ban[x]&&ban[y]&&vis[x][y])ans=-inf;//无解特判
sum+=f[y];
}
if(ban[x])f[x]=1,ans+=sum;
else if(sum>1)ans++,f[x]=0;
else f[x]=!(!sum);
}
无解特判一下有两个关键点连起来没有就行。
__EOF__
- 本文作者: Enoch006
- 本文链接: https://www.cnblogs.com/Enoch006/p/18287472
- 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现