【杂项】刷题日志
前言
感觉有时候自己做已经刷过的题目可能都不一定能刷出来,应该是对题目的理解还是不够深刻,我有想过每道题都写一篇博客,但感觉时间可能不太够,于是我就想对一些新题型和一些比较难的题目写一下博客,其余题目就大概讲一下算法与思路,就放在这篇博客里好了。
- 2023.5.25 佛系更新
关于内容
提供一些大概思路,与每天的做题。
\(2022.10.19\)
\(tarjan\) 缩点之后拓扑跑一遍即可,需要注意的是,拓扑时要把所有点的初值赋为负无穷,起点另外赋值,因为有些点起点到不了的话,就不能算进去。
\(code:\) \(link\)
还是先\(tarjan\) 先缩一下点,很容易想到警察只可能在那些入度为\(0\) 的点上被杀,因为剩下的点都可以提前知道是好是坏,所以我们只要求出入度为 \(0\) 点有多少即可,设为 \(res\),还有一点需要注意的是,如果说有一个连通块只有一个节点,我们可以把它放到最后去看,因为这点的人警察可以直接杀掉(因为如果排查出了 \(n-1\) 个人的身份,最后一个人的身份也知道了,就不用去看了),所以如果有这种情况的话,\(res--\),答案为 $(n-res)/ n $。
\(code :\)\(link\)
比 2-sat 模板还要模板,应该不需要说什么了吧。
\(code:\) \(link\)
\(2022.10.20\)
一开始的思路是\(bfs\)爆搜,但这样肯定不行,考虑优化,发现更新的时候,有很大一段是重复的,因此,由于先更新到的一定最优,所以记录更新到的最高点,每次从最高点开始更新,可以保证每个点只遍历一次,复杂度 \(O(n)\),为什么能这么做呢?因为以更新完的一定是一段连续的区间。
\(code :\)\(link\)
这道题跟P3199 [HNOI2009]最小圈几乎是一模一样吧,双倍经验,嘿嘿,就加个判无解的就行了。
\(code :\)\(link\)
\(2022.10.21\)
\(dfs\),这样可以保证路径上的点深度是单调递增的,且都是在一条链上的。对于一条链上的点 \(x\),把前缀和\(sum[x]\) 放入 \(set\) 中,查询 \(sum[x]-s\) 在 \(set\) 中是否存在,如果有,\(ans++\),退出时,再将 \(sum[x]\) 清出 \(set\),因为 $a_i > 0 $,所以 \(set\) 中没有重复的元素,至此,完美解决。
\(code :\)\(link\)
\(code :\)\(link\)
P6086 【模板】Prufer 序列 :模板题,详见:Prufer 序列
\(code:\) \(link\)
\(2022.10.23\)
李超线段树模板题,记得\(double\) 判相等时要加一个 \(eps\) 。
\(code\): \(link\)
P6033 [NOIP2004 提高组] 合并果子 加强版:
贪心思路想必大家都已经了解了,就是每一次选取最小的两堆果子进行合并,我们可以把这些需要插入的点用一个队列存储起来,首先这些需要插入的点肯定会越来越大, 这相当于延迟插入。当我们目标插入点就是我们当前最小的那一堆的时候,我们就把他插入进来,以上是思路,代码写出来大概就是,桶排,建立两个队列,排序结果放进第一个当中,合并结果放在第二个当中,每次选从两个队列队头选取比较小的合并。
\(code\):\(link\)
P2971 [USACO10HOL]Cow Politics G:
题解
\(2022.10.24\) 快比赛了,就先不发了(主要还是因为自己懒)。
\(2022.10.26\)
今天来刷点CSP-S的真题,话说怎么之前都没去做,为什么现在才去做呢?
\(2022.10.27\)
假设 \(x_i \ge x_j\),可以将原先的式子转化为:\(x_i-w_i \ge x_j+w_j\)。
这样,把每一个点都当成一条线段:\([x_i-w_i,x_i+w_i]\),两条线段相交即两个点有边,原问题可化为:对于这些线段,每条可选可不选,要使它们互不相交,最多能选几条线段?
这就是一个贪心问题,按右端点排序后即可。
\(code\):\(link\)
素数筛+贪心(哈哈,够简短吧)
\(code\):\(link\)
\(2022.10.29\)
CSP 打挂了,接下来一个月好好努力吧,争取 NOIP 取得个好名次。
\(2022.10.30\)
水题,直接模拟,下标最好从 0~n-1
还是水题,开 map 映射,记得题目要看清,有坑点。
P2038 [NOIP2014 提高组] 无线网络发射器选址:
直接暴力枚举。
\(2022.10.31\)
多重背包板子,记得先枚举数量,再枚举体积。
题解区里有bitset做法的,很有启发。
我们用一个bitset保存每个重量能否被称出即可。
#include <bitset>
#include <cstdio>
int a[10], w[10] = {1, 2, 3, 5, 10, 20};
std::bitset<1010> S;
int main() {
for(int i = 0; i < 6; i++) scanf("%d", a + i);
S[0] = 1;
for(int i = 0; i < 6; i++) for(int j = 0; j < a[i]; j++) S |= S << w[i];
printf("Total=%d\n", S.count() - 1);
return 0;
}
二项式定理,不要忘记还有 a和 b。
并查集,把 \(z=0,z=h\) 当成 \(n+1,n+2\) 号点,最后判断是否联通。
手推一下对于高一学生应该不是什么难事。
int 是去掉小数部分,一些细节需要注意,还是有点多的,\(t\) 表示不能接到的前面的最大编号,\(s\) 表示最大的后面的能接到的编号。
#include<bits/stdc++.h>
#define eps 1e-5
using namespace std;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
double H,S1,V,L,K;
int n;
signed main(){
scanf("%lf%lf%lf%lf%lf%d",&H,&S1,&V,&L,&K,&n);
double maxtime=sqrt(H/5);
double mintime=sqrt((H-K)/5);
int s=(int)(S1-mintime*V+L);
int t=(int)(S1-maxtime*V);
if(S1-maxtime*V<(double)t+eps) --t;
t=min(max(-1,t),n-1),s=min(s,n-1);
printf("%d\n",s-t);
return 0;
}
快速幂。
dfs,bfs也可以,这里提供 dp 思路
题目有点坑,看样例就明白了,是有向图,所以用 \(f_i\) 表示以 \(i\) 结尾的最大值,路径记录上一个怎么转移的就好了。
那如果是无向图呢,乱搞吧,反正数据范围这么小。
把每个同学看成一个点,信息的传递就是在他们之间连有向边,游戏轮数就是求最小环。
假如说信息由A传递给B,那么就连一条由A指向B的边,同时更新A的父节点,A到它的父节点的路径长也就是B到它的父节点的路径长+1。
这样我们就建立好了一个图,之后信息传递的所有环节都按照这些路径。游戏结束的轮数,也就是这个图里最小环的长度。
如果有两个点祖先节点相同,那么就可以构成一个环,长度为两个点到祖先节点长度之和+1。
这是一道模拟题,主要需要考虑以下几个约束条件:
1.每个工件的下一个工序必须在上一个工序之后
2.同一台机器同一时刻只能加工一个工件
3.按题目顺序安排下一个工件
按照题目说的做,不会错。
\(2022.11.2\)
给定一个数列 \(b\),问:
1.它的最长不上升子序列长度;
2.最少能被划分成多少个不上升子序列。
Dilworth定理
对于一个偏序集,最少链划分等于最长反链长度。是不是很懵?在这道题中,我可以用人话复述一遍:
最长上升子序列的长度就是能构成的不上升序列的个数。
这样,我们经过简易的推理,就可以得出:
要使用导弹的次数就是最长上升子序列的长度。
如何求最长上升子序列呢?我们把求最长不上升子序列的代码稍微改动一下就好了。
这样,我们就可以在 \(O(n\log n)\) 的时间复杂度内通过本题。
这种题目考场时竟然没打出来,不行了。
现在居然还打炸,原来 5000 * 8000 * log2 8000 会T ,呜呜,只能 \(O(n)\) 来做了。
可以发现,对于一个已经有序的数列,单点修改一个值,我们可以通过前后冒泡各一次来保持有序,举个例子:
原序列为 $ 1,1,4,5,6,7$,修改为 \(1,1,9,5,6,7\)。
我们可以从前往后冒泡,再次维持了数列的有序。这样的操作是 \(\mathcal{O}(n)\) 的。
同样的,我们可以维护一个有序数列,并记录原下标与先下标之间的关系(用数组记录),每次修改后更新这种关系。
这样,修改操作是 \(\mathcal{O}(n)\) 的,查询是 \(\mathcal{O}(1)\) 的。
算出每个价位对应的销量,解不等式,左右区间取绝对值最小的数。
\(2022.11.3\)
建立两个双向链表,一个是对于每一个数的链表,另一个是对于每一块的链表,后者顺便维护每一个块的开始和结束,如果在初始数列的时候这个数等于上一个数,就合并到上一个数所在的块。
每一次操作,先枚举每一个块,删除第一个数,如果这个块删除之后就没了,就在块链表里面把这个块删了;如果删除后前后两个数可以合并,就合并这两个块。
每一个数只需要枚举两次,枚举复杂度稳定 \(O(n)\)。
新学会了 sscanf 与 sprintf 的用法。
可以用 sscanf 尝试读入形如 a.b.c.d:e 的字符串,并返回读取结果。
如果读到的数不是 5 个,肯定不合法。
然后再判断 a,b,c,d,e 的范围是否合法。
这个时候,再用 a.b.c.d:e 把这个字符串保存,判断是否跟原来字符串是否相同即可。
暴力就可以了,复杂度证明。
\(2022.11.4\)
这道题的关键在于,sqrt(1)==1
也就是说,如果一个区间的最大值为1,我们就可以直接跳过这段区间的修改。只有最大值大于1时才有修改的必要。
而题中的数值大小范围在(0,1e12)之间。
每个数至多六次开平方便可得到1
每次暴力修改复杂度为 \(logn\),总复杂度 \(nlogn\) 。数据范围只有 \(1e5\) ,因此不用在意常数的影响。
线段树维护即可。
其实也可以用树状数组维护前缀和,用并查集跳过 $\leq 1 $ 的数。
#include<bits/stdc++.h>
#define N 1000100
using namespace std;
#define int long long
int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') f=(ch=='-')?-1:1,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x*f;
}
long long a[N],b[N];
int n,fa[N];
void add(int x,long long y) {while(x<=n) b[x]+=y,x+=x&-x;}
long long ask(int x) {long long ans=0;while(x) ans+=b[x],x-=x&-x;return ans;}
inline int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);}
signed main() {
n=read();
for(int i=1; i<=n; i++) a[i]=read(),add(i,a[i]),fa[i]=(a[i]==1?i+1:i);
fa[n+1]=n+1;
int q=read();
while(q--) {
int k=read(),l=read(),r=read();
if(l>r) swap(l,r);
if(k) printf("%lld\n",ask(r)-ask(l-1));
else{
for(int i=find(l); i<=r; i=find(i+1)) {
int x=sqrt(a[i]);
add(i,x-a[i]),a[i]=x;
if(x==1) fa[i]=find(i+1);
}
}
}
return 0;
}
要想有解,易知需要满足下面三个条件:
\(1.s_1=1\)
(树肯定是有叶子节点的);
\(2.s_n=0\)(树切完一条边后肯定会分割成两个非空连通分量);
\(3.\forall i \in [1,n-1]\),都有 \(s_i=s_{n-i}\)
(分出一个大小为 \(i\) 的连通分量,意味着一定有一个大小 \(n-i\) 的连通分量)。
这三个条件仅仅是必要条件吗?事实上是充要条件(满足这三个条件一定能构造出符合要求的树)。
下面给出一种构造方案:
我们设字符串中所有值为 \(1\) 的位置下标共有 \(k\) 个,分别是 \(p_1,p_2,\ldots,p_k\)
。
我们先取一条长度 \(k+1\) 的链作为主链,链上的点编号分别为 \(1 \sim k+1\)。
接下来,\(\forall i \in [2,n]\),在主链上的第 \(i\) 个点上挂 \(p_i-p_{i-1}-1\) 个点即可。
易知这种方案一定能凑出 \(s_p=1\) 的所有 \(p\)(得到 \(p_i\)
只需要将 \(i\) 号点和 \(i+1\) 号点之间的边切掉就行),同时保证所有 \(s_p=0\) 的 \(p\) 切不出来。
tarjan 模板,调了半天,竟然是数组开小了。
一是有的罪犯既不能贿赂他也没有罪犯能揭发他,那么此题无解,我们在遍历时打上标记,然后从小到大枚举,只要遇见没有标记的就输出然后退出即可。
二是所有的罪犯都能直接或间接地被能贿赂的罪犯揭发。很明显,也有两种情况,一是没有环,那么资金就是贿赂那个没有入度的罪犯,二是有环,那么资金就是那个环里罪犯所需资金最小的。我们想,如果我们把环里的罪犯缩成一个点,那么全都是前者的情况了。
\(2022.11.6\)
P5968 [POI2017]Reprezentacje ró?nicowe:
找规律,发现每两次会 \(\times 2\) ,\(f_i\) 很快就会超过 \(1e9\) ,所以,只要预处理出 \(f_i-f_{i-1}\) 在 \(1e9\) 范围里的数有 \(pp-1\) 个,剩下数的一定是由相邻的两个 \(f_{pp+i\times2+1,f_{pp+i\times2}}\) 相减得到的,二分一下就行了。
三分模板,但好像二分不仅好写,效率还比三分高,二分 log2,三分 2* log3。
实际上二分,就是一个爬坡的过程——
发现前面大,就往前面爬;发现后面大,就往后面爬。爬到尖顶了,往刚才的方向一看:哎呀!要掉下去了!连忙回缩——无法回缩了,则当前即为最高点
二分
while(l+eps<r){
db mid=(l+r)/2.0;
if(f(mid)<f(mid-eps)) r=mid;
else ans=l=mid;
}
三分
while(l+eps<r){
db len=(r-l)/3.0;
db midl=l+len,midr=r-len;
if(f(midl)>f(midr)) ans=midl,r=midr;
else l=midl;
}
\(2022.11.7\)
简单分析一下就行,我们可以用一下分组思想,把每个颜色分为一组,再在每个颜色中按奇偶分组,所以一共有2m组。在乱搞一下就出来了。
期中考了,晚上就不来了。
\(2022.11.11\)
切比雪夫距离转曼哈顿距离。
P5098 [USACO04OPEN]Cave Cows 3:
曼哈顿距离转切比雪夫距离。
重新学了一下标记永久化。
模板题。
还是模板题。
\(2022.11.13\)
期中考炸了,估计班级倒数,两个班80多名,比上次月考退了50多名。
P4925 [1007]Scarlet的字符串不可能这么可爱:
水题,记得 \(k\) 输进来后先取模。
前缀和优化 dp,应该还是比较好想的。(毕竟我都能场切的 dp,能有什么难的)
FJC 模板,记得局部变量每次要清空,会挂的很惨。
\(2022.11.14\)
CF1732C2 Sheikh (Hard Version):
FJC模板,dp递推很好想,记得ans要清空。
还是FJC模板。
还是模板,你知道是什么吧,记得要开 __int128,不然 long long 会炸。
板题板题。
AT_code_festival_2017_quala_d Four Coloring
曼哈顿路径转哈密顿路径+构造
分段FJC,就行了。
有点难的,FJC优化状压dp,1<<32 可以用 unsigned int 自然溢出,或者用 __int128, long long 会炸。
反悔贪心,注意是环,且 m 个坑必须种满。
基本同上题,改为链,且 m 个坑不一定要种满,即小于 0 是 break。
P1344 [USACO4.4]追查坏牛奶Pollutant Control:
贪心,走路时间不能改变,先以距离排序,那就当时间超了的时候,把前面 AK 时间最久的场给去掉,不到了,可以用优先队列维护,记得如果距离要事先减的话,要倒序,自己想想为什么,要挂成 20 分。
单调性应该好想,但贪心应该比较难,可以把问题转化为,先拿出 \(n+1\) 张牌,再放回去一张,只要判断拿回去的牌数是否小于组数就行了。二分的 r 要大一些。
P3545 [POI2012]HUR-Warehouse Store
:
基本同 小Z的AK计划 ,多了输出路径。
我们先尽可能满足所有我们遇到的顾客,并且使用一个大根堆存放所有你当前满足过的顾客的信息。当我们遇到一个我们无法直接满足的顾客,如果大根堆的堆顶的$ B_i$
小于当前的 \(B_i\)
,我们尝试取出大根堆的堆顶,将它删除并且将它的 \(B_i\)
加入你的存货数量中,然后你继续满足这一位顾客。
由于我们前提条件中有“大根堆堆顶的 \(B_i\)
大于当前的 \(B_i\)
”,如果我们不满足大根堆堆顶的顾客(即当前方案下所有我们满足的顾客中 \(B_i\)
最大的),我们一定是能够用取消堆顶的顾客所拿来的库存来满足当前顾客的,所以我们满足的顾客数量不降,并且你手头上的存货数量还有可能上升,也对答案有正面影响。所以不满足堆顶,满足当前项在这样的情况下是满足贪心的局部最优的。
\(2022.11.15\)
ISAP ,dinic 竟然被卡了,呜呜。
update 11.18 : 发现这道题不卡 dinic,是自己的 dinic 打错了,呜呜。
Hash+权值线段树,枚举中间数 \(a_i\),判断 是否存在
\(a_i-k\) 与 \(a_k+k\) 在 \(i\) 的异侧,将左边的数标记为 1 ,右边为 0 ,判断是否为回文。
\(2022.11.16\)
网络流,建图比较好想。
网络流,建图比较难想,最小割。
费用流,建图时一定要分清点,我调了好久,最小费用最大流开始时 vis[t]=1 而不是 vis[s]=1 。要开 long long 。
这道题真的恶心,竟然禁用的格子有重复,所以不能用 k,而要重新统计。二分图。
二分图,只要判断能匹配到就行了,不需要修改 match 。
二分图求最大匹配,建图可以用染色法。
网络流建图,转化为求最小割模型。
和上一道一模一样,双倍经验。
原来 FJC 不只有加和乘,还可以转化运算符,这样又是一道模板了。
\(2022.11.17\)
网络流经典建图,考虑是输出方案,如果左部点的 d 不为 0 表示没有被割掉,是答案,右部点的 d 为 0 表示被割掉,也是答案,因为最小割保证图最后不连通。
发现有个 t ,不太好处理,那就加上时间维度,发现任意一条路径不会超过 10,于是我们将 10 秒内的状态都加到里面去,再套一个 FJC。
复习一下斜率优化。
对李超的理解又加深了。
斜率优化板题。
网络流的题目 N 和 M 的范围不要开太小,要算清楚,不然会 T。
NOIP了,就先不写。
\(2022.11.20\)
P4402 [Cerc2007]robotic sort 机械排序
哎,还是本来都不想打博客了,但今天晚上被一道题给创死了,调了4个多小时,发现自己在调用 sz 时,没有发现 sz 已经发生那个变化了,上面那个是错误的,下面那个是正确的。
printf("%d ",sz[ch[root][0]]);
int x=tree.find(i-1);
int y=tree.find(sz[ch[root][0]]+2);
int ans=sz[ch[root][0]];
printf("%d ",ans);
int x=tree.find(i-1);
int y=tree.find(ans+2);
呜呜,4个多小时啊,好气啊,傻逼!!!!
\(2022.12.11\)
鸽了这么久了,我终于回来了,NOIP虽然不是很理想,但好歹有一等,继续努力吧。
\(2022.12.18\)
这次是真的。
对于一个要删去的元素,他产生的贡献是在它前面的权值比它大的,且删去时间比他晚的,在它后面的权值比它小的,且删去时间比他晚的,可以把时间也当成一个维度,变成三维偏序模板,用 cdq 就行了。
P2880 [USACO07JAN] Balanced Lineup G :
RMQ 模板,线段树,ST表均可,我这里为了复习一下 ST 表,就打了。
第一眼看成区间众数,其实没那么难,以为是单调不减的,相等的一定相连,所以转成求区间最值,在判断一下边界情况
二位区间最值,因为 n 的范围小,可以每行每行来做,又转成区间最值,用 ST 表维护即可。
注意
for(int i=2;i<=n;++i) lg2[i]=lg2[i-1]+(i&(i-1)?0:1); //是 i-1,不要手残打成 i
学了 tarjan 的离线算法
可以转化成从 n 个数中选出 2 个数 使得异或答案最大。
可以使用 01 trie + 贪心处理。
P3657 [USACO17FEB]Why Did the Cow Cross the Road II P :
树状数组优化近似LCS的dp。
P6119 [USACO17FEB]Why Did the Cow Cross the Road II G :
双倍经验。
\(2022.1.7\)
吉司机线段树模板
这道题调了好久,才发现是 update 时, len 的长度搞错了,拿了一整段在那里搞,所以需要用长度时,最好事先记录好 l, r ,最后再减一减不容易出错。
\(2022.1.8\)
$ \displaystyle \mathrm{ans} = \frac{n!}{m!} \cdot \varphi(m!)$
需要注意的是,预处理的时候,如果有乘的数能整除 \(R\) , 那么需要先把那个数把 \(R\) 因子全部除掉再计算,最后特判一下,如果 \(\lfloor \frac{N}{R}\rfloor>\lfloor\frac{M}{R}\rfloor\) (即 \(N!\) 中 \(R\) 因子更多)则答案为 \(0\) ,否则 \(N!\) 中的 \(R\) 能与 \(M!\) 中的消掉,就不能为0 .
感觉现在就是凭心而写,想写就写,不想写就不写
\(2022.1.9\)
将状态设计成点,跑 spfa 就行了,注意位运算的优先级,不确定时加个括号最好了。
差分约束板题,可以将条件转化成三角形不等式,跑 spfa,最后答案就是 dis数组,含有这数据也太水了吧,我 边数写成 n ,居然都有 90 分。
双倍经验,注意答案不能为负数。
二分 + dijkstra
好题,转换思路,与其设置边权使路径长度相等,不如设置路径长度去拟合边权的限制。
设 \(d_i\)
为 \(1→i\) 的最短路,只需保证对于所有边 \((u,v)\) 有 \(w(u,v)=d_v−d_u\) 即可使任意一条简单路径长相等。于是 \(1 \le d_v−d_u \leq 9\) ,转化为 \(d_u+9 \ge d_v\) 与 $ d_v−1\ge d_u$,差分约束求解即可。注意不在 \(1→n\) 的任意一条路径上的边没有用,这些边不应计入限制,随便标权值,浅浅来个 rand。
几乎同上题。
差分约束
\(2023.1.10\)
差分约束,第九个点有点恶心,就是注意给 dis 赋值时不要覆盖,可能事先有 dis[b][a] = -1, 就不用赋值 dis[b][a] = 0。