暑假模拟赛总结
csp-j模拟赛2
A 公式求值
加入前缀和思想的高精度加法。
B 最长的Y
我永远喜欢 IOI 赛制。
考场写了两份代码,调了两个小时,结果到最后 10 分钟发现第一个代码能够 subtask 1,第二个能过 subtask 2,于是结合起来喜提 \(60\) 分。
我们先找到每一个 \(Y\) 块,然后循环找到左右两边离他最近的 \(Y\),更新块长度和答案,直到耗尽 \(k\),对于每个块更新答案即可。
C 交换序列
四维 dp??
D 最长路径
三维 dp??
csp-j模拟赛3
A 分糖果
气死人了,没仔细检查结果全 WAAAAAA
要把两种情况分类讨论,同时计算取最大值。
B 乒乓球
这是一个很巧妙的找规律,考虑到超过 \(11\) 分的比分性质相同,可以直接把类似于 \(114:115\) 当成 \(10:11\),这样规律就比原始的比分更好找了。
C 与或
直接状压暴力。
简单来说,观察到 \(%30\) 数据的 \(n,k \le 20\),而且 \(2^{20}=1048576\) 我们便可以想到把 \(\&\) 和 \(|\) 的状态压缩成一个数。
因题目要求 \(|\) 的字典序小于 \(\&\),那么可以令这一位的 \(|\) 表示 \(0\),\(\&\) 表示 \(1\),那么序列 \(\&\&|\&\&\&\&\&\&|\&|\&||\&|=11011111101010010=114514\)
在新的位置添加一个 \(|\) 就是 \(S<<1\),添加一个 \(\&\) 就是 \(S<<1|1\)
最后在更新答案的时候可以直接比较数字的大小而不是遍历全部数组。
这样能让暴力更快一点(指快一两毫秒)
D 跳舞
要预处理每两个数的 \(\gcd\),时间复杂度 \(O(n^2 \log n)\),然后就是类似与区间 dp 的东西了。
E 音乐播放器
概率 dp。
csp-j模拟赛4
A 超市抢购
这就是小孩题,但因为随机数生成器的问题卡了 \(\text{long long}\)。
首先题目给出的随机数生成器是这样的:
int randseed;
unsigned int rnd()
{
unsigned int r;
r = randseed = randseed * 1103515245 + 12345;
return (r << 16) | ((r >> 16) & 0xFFFF);
}
- 首先观察到这个子函数的类型的 \(\text{unsigned int}\),\(32\) 位无符号整型,即最高一位代表数字而非符号。
这样是为了防止溢出成负数,也是方便设置生成数字的位数为恰好 \(32\)。
但是我们观察到一个细节,就是 \(randseed\) 这个变量是 \(\text{int}\) 类型而非 \(\text{unsigned}\),并且循环中输出的 \(randseed\) 也大多是负数。
原因很简单,\(\text{int}\) 和 \(\text{unsigned int}\) 存储的形式是相同的,没开 \(\text{unsigned int}\) 可能单纯是不想,但是我们 \(r\) 考虑的就要多了,所以必须得是 \(\text{unsigned}\)。
- 然后,观察到这个式子:
r = randseed = randseed * 1103515245 + 12345;
这是很古老的一种随机数生成算法,一种最简单的单状态 LCG(线性同余生成器),至于是啥,咱也不知道。
其中用到了 \(1103515245\) 和 \(12345\) 这两个数,我在网上找到的解释是
事实证明,rand() 的每个连续输出的低位将交替(例如,0,1,0,1,0,1,...)。
你明白为什么吗? x * 1103515245 的低位与x的低位相同,然后加 12345 只会翻转低位。因此,低位交替
- 最后看到
return
\(0xFFFF=65535=2^{16}-1\),并且 \((r << 16) | ((r >> 16) \& 0xFFFF)\) 使得或运算左边的表达式的低 \(16\) 位都为 \(0\),或运算右边的表达式只有最低的 \(16\) 位。
这样与起来的长度必定大于等于 \(32\) 位,关键来了。
如果返回值是 \(\text{unsigned long long}\),那么 r<<16
是会超过 \(\text{int}\) 的,因为不能保证 \(r\) 的值必定小于 \(65536\)。
如果返回值是 \(\text{unsigned int}\),那么会自动溢出,直接将高位舍弃,保留低位。
这样就是不能用 \(\text{long long}\) 的原因了。
但事实上,那个 &0xFFFF
的运算是无意义的,因为你的 \(r\) 最大也是 \(32\) 个 \(1\),右移 \(16\) 位必定小于 \(65535\) 了。
但事实上,如果把 &0xFFFF
放在或运算的左边,也就是这样写,可以使得返回值严格在 \(\text{unsigend int}\) 中:
(r & 0xFFFF) << 16 | (r >> 16)
这样会先使 \(r\) 只有最多 \(16\) 位,再右移 \(16\) 位那肯定小于 \(\text{unsigned int}\) 的最大值而不会溢出了。
但事实上,这样修改随机数生成器,还是保留 #define int long long
也是不行的,
原因很简单,\(r\) 在和随机数种子生成随机数的时候就可能已经溢出了,要想完美解决,这样写就好了:
B 核酸检测
考场上推出 \(O(NM)\) 的 dp 了,可惜死活没想到要把区间按左端点排个序,没想到和玮的思路一模一样,稍稍优化一下就 A 了、
首先,考虑状态 \(dp[i][j]\) 表示把前 \(i\) 个人都喊下来且最后一次喊在 \(k\) 时刻的最小次数,\(g[i][j]\) 表示其方案数。
容易得到:
- 当前扫描到第 \(i\) 个人,在第 \(j\) 时刻时,如果 \(dp[i-1][j]\) 有值,那么相当于和上一个人情况相同。
- 否则,可以把从 \(1\) 到 \(i\) 所有人的区间看成两个部分:前 \(i-1\) 和第 \(i\),容易得到:
对于 \(g[i][j]\),和上面类似:
- 当前扫描到第 \(i\) 个人,在第 \(j\) 时刻时,如果 \(dp[i-1][j]\) 有值,那么相当于和上一个人情况相同。
- 否则,可以把从 \(1\) 到 \(i\) 所有人的区间看成两个部分:前 \(i-1\) 和第 \(i\),每一种都能从可转移的位置得到,容易得到:
优化一
有这样一张图:
可以看到 \(dp[i][j]\) 的值无非就是那两种,所以我们可以在处理上一行的同时直接处理出 \(\min(dp[i-1][j])\)
这样,查询最小方案的时间复杂度就来到了 \(O(NM)\)。
优化二
和上一优化一样,\(g[i][j]\) 的值也只有两种,因为,如果 \(g[i-1][j]\) 有值,那么 \(dp[i-1][j]\) 也必有值,也就是图中左边的情况。
否则,\(g[i-1][j]\) 没有值,\(dp[i-1][j]\) 也必定没有值,对应图中右边情况,这时的 \(dp[i][j]=\min(dp[i-1])\),我们可以发现一个关键:
如果 \(g[i-1][j]\) 无值,那么\(g[i][j]\) 所对应的所有 \(dp[i-1]\) 都一样 \(=\min(dp[i-1])\)。
我们可以 \(O(M)\) 地在每次计算完后处理出所有满足 \(dp[i-1][j]=\min(dp[i-1])\) 的 \(g[i-1][j]\) 之和。
因为这种情况一定在上一个区间的右端外并且不重合,所以这种情况的 \(g[i][j]\) 一定相等,等于我们预处理出的 \(sum\)。
这样,我们也实现了 \(O(NM)\) 的查询方案组数,总体时间复杂度来到了可喜的 \(O(NM)\)。
优化三
虽然但是,时间复杂度刚好能过,但空间复杂度开始爆炸。
观察我上面写的所有式子,对于第 \(i\) 个人,他的所有答案、贡献只和 \(i-1\) 个人有关,那还怕啥,直接滚!
滚完之后就 ok 了
实际得分是 \(100\) 分,研究了一下,发现汇总答案确实需要第 \(n+1\) 个人,然后 第一个输出 \(ans-1\)。
C 七龙珠
不太会啊,看了题解也没思路。
考场推 \(k=1\) 情况的部分分,结果寄了。。
D 龙珠游戏
dfs 写假了,急急急。
csp-j模拟赛5
A 算术求值
妈呀,想复杂了,但是时间复杂度很优秀啊 hhhhhhh
考场上因为取模一直卡在 \(90\) 分,硬控半个小时。写到 T2 突然回光返照想起来补了取模就过了。。
考虑这样一个事情:这样的一个算式其实等同于一个线性的周期函数,周期为 \(T=998244353\),定义域为 \((-\infin,+\infin)\),值域为 \([0,998244352]\)。
但实际上的定义域,是 \([1,n]\)。我们需要在闭区间 \([1,n]\) 上找到 \(f(x)=kx+b\) 的极大值,这里的函数 \(f(x)\) 可以直接通过题目输入转化出来,很容易实现。
我们会遇到两种情况:
- 定义域横跨两个周期
- 定义域在一个周期内
第一种就是题目样例 \(x-6\) 的情况,在 \(f(5)\) 处能取到原函数的极大值。
我们可以这样判断:
首先令 \(f(x)_{\max}=m\)
注意这个 \(m\) 是一个取值的集合,我们这样表示:\(m=998244352+k998244353 , k\in \Z\)。
回到刚才的函数,我们循环 \(m\) 的取值,并令
\(kx+b=m\)
\(x=\frac{m-b}{k}\)
每得到一个 \(x\) 就和定义域 \([1,n]\) 比较,如果在定义域内就更新最大值,准确来说是 \(f([\frac{m-b}{k}])\)
如果当前的 \(x\) 已经超出定义域,那么我们可以断定:定义域内不存在 \(f(x)\) 的最大值。
这就是定义域被包含在一个区间内的情况,因为 \(f(x)\) 在每个区间单调递增,所以这个情况的最大值就是在 \(f(n)\) 处取得。
这样就把所有情况讨论尽了。
找 \(k\) 和 \(b\) 的时间复杂度是近似 \(O(s)\),找答案是时间复杂度基本上 \(O(1)\),不会跑几次。
B 括号序列
直接栈+贪心 \(O(n)\) 战神一遍过。
C Count Multiset
神秘 dp,我不会,考场上还是普普通通的暴力 \(10\) 分。
D 选数
据说标程的时间复杂度已证伪,太吓人了,我选择 \(O(n^2 \log n)\) 暴力+二分答案。
E 划分序列
dp+二分答案区间,老套路看来得好好回顾一下。
csp-s模拟赛1
A 最短路 (path)
正解是 floyd,稍稍修改一下就能过,考场上没看出来,硬是打了一个 \(dfs\) 加树剖过了 \(30\)……
B 方格取数(matrix)
所有暴力终将会被绳之以法!
\(n^2\) 过百万,暴力碾标算
正解是单调栈/悬线法复杂度 \(O(n^2)\),但是昨天晚上也是把 \(O(n^4)\) 代码卡过去了。
晚上回去想了一下,最后一个大点怎么会是 \(-1\),而且 \(-1\) 的情况有哪些,昨天考场上想出来的都是能预处理的,现在我总结一下:
- 矩阵所有元素都大于 \(2k\)
可以预处理提前计算。
- 矩阵所有元素之和小于 \(k\)
同样前缀和一下就能判断。
- \(k=2\) 且矩阵中所有 \(1\) 的上下左右没有 \(1\)(是在矩阵没有单另的 \(2,3,4\) 前提下)
输入的时候可以预处理
- 其他情况
这里主要说的就是这个其他情况,因为前面说的 \(4\) 种 \(-1\) 都可预处理,只有这个不行。
考虑这样一件事:如果矩阵中的元素有 \(n^2-1\) 个都是 \(>2k\),只有一个 \(<k\),那么它肯定不符合,并且要得出答案必然要遍历全部矩阵,跑满 \(O(n^4)\)
或者这种情况,\(2k+1\) 和 \(k-1\) 交替出现,也是上述情况。
5 3
2 7 2 7 2
7 2 7 2 7
2 7 2 7 2
7 2 7 2 7
2 7 2 7 2
可以通过数据生成器来做出这种数据的完全体:
#include<bits/stdc++.h>
using namespace std;
int n,k;
int main()
{
freopen("in.txt","w",stdout);
srand(time(NULL));
n=2000,k=10000000;
cout<<n<<" "<<k<<endl;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if((i%2==1&&j%2==1)||(i%2==0&&j%2==0)) cout<<k-1<<" ";
else cout<<2*k+1<<" ";
}
cout<<endl;
}
}
但是又会想到,遍历全部矩阵的过程是一个严谨的逼近答案的过程,当循环次数趋近于无穷大而没有合适的子矩阵时,答案是 \(-1\) 的概率也会趋近于 \(1\),所以我们在循环中加入卡时代码是不无道理的 ,嘿嘿
那么能完全 hack 的数据应该把答案分布在最右下角,并且有唯一答案,这样也是会跑满的。
C 数组(array)
拿两个线段树分别维护区间乘和区间取模,统计每个数的质因数分布,用 \(\text{long long}\) 状压维护一下。
考场上一直没想到用欧拉函数定义式去算,一直想着线性筛。。。
D 树(tree)
服了,勾式细节,考场上都把 \(40\) 代码码出来了,结果细节不到位,每个点都差一点,细节啊细节。。
正解是长链剖分+根号分治,比较神秘,我看一个人写的序列分块+树剖的代码还挺好。
csp-s模拟赛2
A 炒币
直接 \(O(n)\) 贪心就好。
每一天的汇率类似于一种波浪形函数,有多个单调下降区间。
类似于线性 \(dp\) 求解单调下降子串,对于一个单调下降区间,换成 \(BTC\) 的一定是波峰,换成 \(RMB\) 的一定是波谷,而且对于每个区间一定要换,才能保证最大。
B 凑数
根号分治物品,也是贪心。
背包有 \(30\) 分但是我不知道什么情况而挂零了。。
C 同构
D 最近公共祖先
我要疯了竞赛放假学校刚好上课这样暑假就被报销了啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
csp-s模拟赛3
A 道路
这就是个结论题,但是没看出来,偶数长度边会变成 \(\frac n 2\),奇数长度的边会变成 \(\frac{n+1}{2}\)。
从这里去考虑对整体的贡献会发现只和深度的奇偶性有关。
B 集合 (set)
0/1 Trie 大法。
C 科目五(drive)
二分答案就好。这几天每天都有一个题可以二分答案拿到满分(部分分)
看来以后得好好注意一下。
D 监狱(prison)
线段树优化建图呃呃
会出 No 的情况只有两种。
-
同向包含
-
异向终点相交
但是实现只会链上的,树上的还是不太会啊。
csp-s模拟赛4
一分是怎么差的。。
A 传送带
考场上想出来一个 \(80\) 分代码,但是因为一些奇妙优化忘了加而挂到 \(60\)。
搞一个类似于分块的东西,维护联通的 \(<\) 和 \(>\),每次转折都会走到上次转折点的下一个(前或后与方向有关)
然后每次转移都是 \(O(1)\),总体复杂度 \(O(nk)\)。
lhx 的正解非常强大,hack 了一下午都没搞掉。
B math
数论题,考虑方程 \(ax+by=z\) 有整数解当且仅当 \(\gcd(a,b)|z\),那么能凑出一个 \(z\) 且 \(\sum a_ib_i=z\) 当且仅当 \(\gcd(a_i)|z\)。
所以全部数组元素 \(a_i\) 的 \(\gcd\) 的倍数都可以被凑出。
我是从序列本身考虑的,首先给 \(a\) 数组模 \(k\),然后再去重,用 \(umap\) 记录,因为每个数不超过 \(k\) 的倍数都能凑出,凑齐倍数之后,再 \(o(n^2)\) 的找出两数之和,打进去就完了。
多 \(sort\) 了几次保证答案单调(题目无 spj)。
考场上 \(45\) 分,去掉一个优化之后 \(69\) 分。。。。
C biology
考场暴力打爽了,10 分钟就打完了。
D english
考虑小点暴力 \(O(n^2 \log n)\),只有 \(0/1\) 的点随便推推结论就出来了。
csp-s模拟赛5
今天的题顺序比较迷惑,但是根据下发的文件可以轻易推断出顺序:T3 T2 T4 T1。
A Black and White
B White and White
前 \(10\) 分就是前缀和 \(O(n)\) 一下。
C White and Black
考场想了一个迷惑做法,大样例一直卡在 \(1.4\) 秒,交上去全 T。
先树剖,再建立一棵主席树维护每个询问的集合,然后 dfs,到一个黑点就区间修改子树(区间异或),如果子树为空就直接返回。 时间复杂度 \(O(nq\log n)\)
感觉比这个要小,因为如果 \(q\) 大了那么每个询问的 \(m\) 就会小,空子树太多能剪掉很多枝,但是还是会 T。
正解和我预想的一样,是结论,推一下就出来了,不做赘述。
D Black and Black
括号序列的 \(10\) 分很好想,要加起来等于零就得 \(\displaystyle\sum_{a_i=-1}b_i=\sum_{a_j=1}b_j\),但是如果你的 \(-1\) 都在一坨,且 \(b\) 严格单调,那肯定是不行的啊。
构造题,随便构造就好了。
把最后一个 \(a_n\) 钦定成 \(1\),这样如果前面的排列小把前面前缀和刚好到 \(1\) 的位置减去一个极大数,满足 \(\infin+\infin=\infin\) 的极大数,给最后一个数匀过去就好。
csp-s模拟赛6
A Permutations & Primes
其实是找规律题,多打几个表找找共同的规律即可。
B 树上游戏
二分答案啊。写了半天只有 6 分。。
C Ball Collector
D 满穗
原来你也玩饿殍
多跑几次暴力(跑了十几次)你会发现:答案只有至多 \(3\) 种,视你的数据强度而定,但上限就是 \(3\) 种。
在观察每个答案能取到时的 \(a[1]\) 的值,你能得出例如这样一张表:
\(8\) 和 \(94\) 代表答案,而 \(pos1\) 则是这次 \(a[1]\) 的值,因为数据强度问题只出了两种,实际上确实是三种。
你会发现较大的答案会分布于较小的值处,较小的答案分布在较大的值处。
在 \(m\) 的范围趋近于无穷大的时候,这些答案的区间应该是趋近于包含完整个值域 \([-10^9,10^9]\) 的。
得出这样一个结论:每个答案的区间是连续的,并且随答案的减小,区间在数轴上的位置单调递增。
鉴于答案种类极少,我们可以直接记录答案和每个答案的区间,
-
若有新答案,++cnt 并输出当前计算的答案;
-
若是旧答案,但不被包含在区间内,则扩展当前答案区间;
-
若是旧答案,且被包含在区间内,则直接输出无需计算。
这样虽然理论时间复杂度仍然是暴力,但剪掉了相当多的一部分计算量,可喜可贺。
csp-s模拟赛7
真爽了,难得打到了 rk 4。。。
A y
这题不难,两种思路。
第一种就是找全部点到一顶点的距离,排序输出,时间复杂度 \(O(nm\log nm)\)
第二种就是我这种,找规律,画矩形,每次往外扩,分了 \(4\) 类去讨论,时间复杂度 \(O(nm)\)。
B s
dp 或组合,?
C p
50 部分分:
- 树是一条链
直接从询问区间的左右端点找到第一个状态是 \(1\) 的点,答案就是 \(r-l\)。
- 满足 \(\forall l=1,r=n\)
这个比较复杂,我们这样考虑:
若当前构成答案的两点是 \(x,y\),那么现在有一个新的点 \(z\),我们就可以查询两次长度 \(dis(x,z)\) 和 \(dis(y,z)\) 与现在的最大答案 \(dis(x,y)\) 比较,更新更优的答案,时间复杂度 \(O(\log n)\)。
如果有一个 \(1\) 点被改成 \(0\) 了,就看这个点是不是我们现在的答案,如果是,就得找一个新的答案,时间复杂度 \(O(n\log n)\)。
如果没有答案,直接 \(O(n^2 \log n)\) 去找,找到就记录,查询时直接输出即可。
简单来说就是通过每次更新 \(1\) 的点来更新答案,查询时基本上是 \(O(1)\),总体时间复杂度近似于 \(O(n\log n)\),只是近似。
D m
csp-s模拟赛8
A 随 (rand)
zz 期望题,
B 单(single)
这个题很好啊hhhh。
考虑 \(op=0\),根节点的答案就是 \(\sum a[i] \times dis(1,i)\)。
那么每进到一个子树中,会使子树中的距离减一,子树外的加一,用前缀和计算一下就行。
时间复杂度 \(O(n)\)。
其实 \(op=1\) 的情况有点类似。
C 题(problem)
类似于卡特兰数的东西。
有 \(\frac n 2\) 的步数在向外走,剩下 \(\frac n 2\) 的步数往回走。
D DP搬运工1
暴力优化加打表有 28 分。
csp-s模拟赛9
A 花间叔祖
很巧妙的一个数学题。
假设 \(\forall a_i \equiv y \pmod k\),那么我们可以得到:
我们只需要将 \(a\) 数组相邻的两两相减,取绝对值并取全部的 \(\gcd\),就可得到上述的 \(k\)。
如果 \(k=1\) 就证明最小的模数是 \(1\),因为要求模数大于等于 \(2\),就取 \(2\) 了;
否则输出 \(1\)。
B 合并r
神奇 \(dp\)。
我们设 \(dp_{i,j}\) 表示使用了 \(i\) 个数,现在总和为 \(j\) 的方案数。
可以想出 \(dp_{i,\frac j 2}\) 能由 \(dp{i,j}\) 得到,并且可以补 \(1\),就是 \(dp{i,j}\) 可以从 \(dp{i-1,j-1}\) 得到。
我们可以得出:
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=n;j>=1;j--)
{
dp[i][j]=(dp[i][j]+dp[i-1][j-1])%mod;
if(2*j<=n) dp[i][j]=(dp[i][j]+dp[i][j*2])%mod;
}
}
cout<<dp[n][k];
C 回收波特
如果两个点对于原点对称,那么这两个点以后的所有的行动都是对称的,可以一起统计。
D 斗篷
奇妙的 \(linux\) 和 \(windows\) 产生了许多不可描述的化学反应,导致很多人都挂分在输入上。。。
可以预处理出每个点 \(-\) \(\text{\\}/\)的前缀和,这样查询就是 \(O(1)\)的,把 50 分优化到 65 分。
inline int ck(const int len,const int x,const int y)
{
if(y-4*len<=0) return 0;
if(x-2*len<=0) return 0;
if(cnt_[x][y]-cnt_[x][y-4*len]<3*len) return -1;
if(cntr[x][y]-cntr[x-2*len][y-2*len]<len) return -1;
if(cntl[x][y-4*len]-cntl[x-2*len][y-2*len]<len) return 0;
return 1;
}
正解是要加上数据结构的,类似于树状数组或扫描线,把这张图放到一个二维平面、画成一个点阵图去搞。
csp-s模拟赛10
A F
正解没看懂,但是暴力 dfs 过了。。
以为只有 30 分,结果满了。
B S
dp,和考场想的有点像,设 \(dp_{i,j,k,1/2/3}\) 分别表示 \(3\) 种颜色填到哪里了,最后一个是什么颜色,逆序对数最少是多少。分别维护,加和取最小值即可。
C Y
一坨。
D O
妈呀,考场写了个主席树,结果一直 18 分,把操作离线之后直接 54,血亏。。。。
观察到这个题需要维护很多个版本,而每个版本 \(i\) 只与上个版本 \(i-1\) 有关,并且有转移:\(a_i=\max(a_{i-1},a_i)\),这个 \(\max\) 里面的代表上个版本的值。
观察到:有的点可以继承上个版本当前位置的信息,有的点需要新建信息,我们便可以构建一棵可持久化线段树,也就是(广义的)主席树。
我们可以将询问离线,以 \(t\) 为关键字排序,这样得到的版本号是单调不降的,便于更新(事实上如果不离线会出很多很多问题,导致我考场只有 \(18\) 分)。
手造几组大样例可以得到这样的信息:
每个版本的数列信息会在一定操作后保持不变。
这句话的意思是如果当前版本的序列是单调不降的,那么从它以后所有的版本都和它长得一样。
我们可以从这里出发来优化。
考虑在每次更新版本信息的时候记录当前版本是否单调不降,如果是,直接退出,记录这个版本号 \(x\),以后要是有版本 \(t_i\ge x\),直接查版本 \(x\) 的值即可。
这样我们在面对一个查询操作,就有这样的分类讨论:
- 若已经计算的版本号大于当前询问的,直接查询询问版本号的信息;
- 若询问的版本号大于单调不降的版本号,直接查询单调不降的版本信息;
- 否则,更新版本信息,直到当前询问版本。
这样我们就讨论完了,空间复杂度 \(O(n\log n)\),时间复杂度 \(O(tn\log n)\),其中 \(t\) 代表最大查询的版本号,或单调不降的版本号。
期望得分是 \(36\) 分,实际得分 \(54\) 分,因为有两个大点本身是单调不降的,版本只剩下一个了。。。
csp-s模拟赛11
果然,早上四点半骑车会有魔法加持。考场盯出来 T1 和 T4 的正解。。。
A 黑客
差一分钟拿到首 A。
一眼,盯着看 5 分钟就会了。既然要约分,不如我们直接枚举约分后的结果,看有多少能匹配进区间内,统计即可。
时间复杂度 \(O(n^2 \log n)\),这里 \(n=998\)。
B 密码技术
没想到正解竟如此简单。
考虑合并所有能换的行与列,每个并查集的贡献是 \(siz!\),总共的答案就是 \(\prod siz!\)。
C 修水管
想了一个小时,没结果。。
概率期望还是不适合我啊啊啊。
D 货物搬运
正解考场推出来了,写的比较绕,没想到用 \(deque\) 维护每个块的状态,血亏 \(80\) 分。
对于每一次移动,都会使第 \(r\) 个数移动到第 \(l\) 个数,并且第 \([l,r)\) 的数分别向后移动一格。
因为线段树等数据结构无法满足区间平移这项操作,我们便可以考虑万能的分块。
首先,要分块需要找到能对于一个整块直接计算答案的东西,我们可以做一个桶,设 \(b[i][x]\) 表示第 \(i\) 个块内数 \(x\) 的出现次数。
因为 \(x\in [1,10^5]\) 且 \(i\in [1,320]\)(大概),能开下,所以分块可做。
接下来分类讨论怎么 \(O(\sqrt{n})\) 维护区间平移。
首先我们要知道 \(deque\) 可以直接访问或修改每一个节点,且若删去 \(front\),其他元素可以自动对齐,这样 就能非常方便的辅助我们进行操作了。
- 修改在同一块中
我们用一个栈来辅助。先将点 \(r\) 压进栈中,再把区间 \([l,r)\) 的所有数也放进去,这样栈底就是 \(r\),其他元素的位置都向后移动了一个。
再倒序 \([r,l]\) 将栈顶元素存入队列中,完成操作。
- 修改在不同块中
首先考虑左端点,左端点的栈中栈顶(也就是最后一个入栈的)一定要是 \(r\),这样在重构区间 \([l,ed[bel[l]]]\) 的时候能保证第一个放进去的就是 \(r\)。
我们会注意到:这个块中会多出一个元素,而转移后的末尾块会少一个元素。
多出的元素是最后一个,少的元素也应是第一个,我们在中间的整块中,可以通过继承上一块队尾为队首,传递本块队尾为下一块队首的思路进行传递。
在 \(r\) 所在的块中,要提出来从 \([st[bel[r]],r]\) 的所有元素,依然是顺序塞进栈中,这时候的 \(r\) 应该已经在 \(l\) 所在块了,所以需要将 \(top-1\),忽略它,再顺序填入,就维护好了。
需要注意的是,每次操作都要改 \(b\) 数组。
这样时间复杂度就是 \(O(n\sqrt{n})\)。准确来说,是 \(O(n\sqrt{n}+q\sqrt{n})\)。
暑假提高组图论专题1
F 「JOISC 2014 Day2」水壶
先开的这道题,感觉很可做。
首先,需要将这个矩阵转化成一张图,计算完全源最短路。
如果使用单源 \(bfs\) 去跑,或者 \(A*\),都是近乎 \(O(NMP)\) 的,不足以通过。
那么我们就需要搞出一个比较神奇的算法:多源 \(bfs\) 了。
考虑将所有建筑物先加入队列,并每次取出队头向外扩展,那么可以将从第 \(i\) 个建筑物扩展出的点标记为 \(i\),意为属于点 \(i\)。
再记录每个点从出发点走来的路程 \(dis\),
如果两个出发点不同的点 \((x_1,y_1),(x_2,y_2)\),不妨设分别为 \(i,j\) 遇到了,那么这两个出发点的距离 \(=dis(x_1,y_1)+dis(x_2,y_2)\)。
因为如果你俩是第一次遇到的,证明你俩就是离起始点最近的两点,很好解释。
就比如说你在 MC 的空地上同时放很多桶水,那么某两桶水第一次相遇的那部分所走的路程就是这两点的最短路径。
建完图后,看题目中说要找两点道路中路径的最大值的最小值,我们就可以把这张图换成一棵最小生成树,然后树剖求 \(lca\) 并用线段树求区间最大值。
但是由于树剖和线段树的空间都比较松弛紧凑,因此线段树得动态开点,减少空间。
这样我们就得到了一份 4.35k 的 AC 代码,因为 AT 只能开 O2,而 O2 会增加空间使用,所以只再 OJ 上 A 了。
时间复杂度约为 \(O(nm+p\log^2 p)\)。
还有个小问题,就是你的 kruskal 跑出来有可能是个森林,所以你需要建一个虚拟原点来连接所有的并查集,树剖从虚拟原点开始跑,但是时间戳要从 \(-1\) 开始,这样才能在线段树的时候不出怪问题。
csp-s模拟赛13
A 怎么又是先增后减
有个一眼的结论:若每次可交换两相邻元素,则把一个序列搞成单调不降或单调不增序列的次数为次序列中反向或正向逆序对的个数。
这就好办了,上动态开点权值线段树维护逆序对,统计即可。
B 美食节
鬼能想到这玩意是个贪心!
贴一个 why 的图:
C 环上合并
没想到写 \(m\le 3\) 的情况假了,急急急,
想着搞一下贪心加上暴力,只过了单调不降的点,
D 送快递
csp-s模拟赛14
A 逆流而上
考 场 挂 分 神 器。
其实还是逆序对。
改 \(10\) 是少一个逆序对,改其他的是少两个逆序对。
所以如果逆序对的个数是 \(3\) 的倍数,那么 B 国可以刚好改完。
否则是 \(A\) 国改完。
B 致命冲击
正解是线段树,分类讨论每种情况的边界,然后去 pushup。
C 帝国飘摇
考虑每个块的最坏情况是最大和最小分、第二大和第二小分、第 \(k\) 大和第 \(k\) 小分。这样得出的结果就是最差的。
D 通天之塔
csp-s模拟赛16
A 串串
这个是求有的多少个回文串的,先用哈希或者 Manacher 求出回文串,再去看是否符合要求。
Manacher 模板:
#include<bits/stdc++.h>
using namespace std;
const int N=2e7+101;
char c[N<<1];
int p[N<<1],cnt,ans;
inline void read()
{
char ch=getchar();
c[0]='~',c[cnt=1]='|';
while(ch<'a'||ch>'z') ch=getchar();
while(ch>='a'&&ch<='z') c[++cnt]=ch,c[++cnt]='|',ch=getchar();
}
signed main()
{
read();
for(int t=1,r=0,mid=0;t<=cnt;t++)
{
if(t<=r) p[t]=min(p[(mid<<1)-t],r-t+1);
while(c[t-p[t]]==c[t+p[t]]) ++p[t];
if(p[t]+t>r) r=p[t]+t-1,mid=t;
if(p[t]>ans) ans=p[t];
}
cout<<ans-1;
}
B 排排
妈呀,没想到果然是结论题,考场上也是压线提交(11:29:59)得了 \(82\) 分。
答案只有四种,就是 \(0,1,2,3\),其中 \(0\) 不用说,就是排好的。
\(1\) 是要满足 \(\exist x,P_x=x\),并且 \(\forall i<x,P_i<x,\forall j>x,P_j>x\)。
\(3\) 是当且仅当 \(P_1=n,P_n=1\) 时成立。
其他情况就是 \(2\)。
C 序序
D 桥桥
大码力题。把操作分块,然后用可撤销并查集维护询问和修改。
csp-s模拟赛17
今天是 Gal game 集合。。。
难道lhb喜欢这些???
A 九次九日九重色
想到 LIS 了,但是没想到怎么求。。
每个数的所有倍数总数是 \(O(n\log n)\) 的,把所有可能配对上的数对 \((i,j)\) 对 \(i\) 排序,然后给 \(j\) 求 LIS 即可。
LIS 要用 BIT 或者线段树优化到 \(O(n\log n)\)。
B 天色天歌天籁音
一眼区间众数,但是写了个假的莫队,是拿动态开点权值线段树维护的,其实只需要开两个数组,分别维护第 \(i\) 个数有几个,有 \(i\) 个的数有几个,每次修改可以 \(O(1)\) 实现。
谷上还是个紫,我看最多是个绿、
C 春色春恋春熙风
粘上昨天码的 Manancher,结果还全 WA。
树上启发式合并。。
回文的判定,无非就是当所有字母全为偶数个,或者有且仅有一个字母为奇数个,可达成回文。
那么我们可以参考异或的定义,让奇数个表示为 \(1\),偶数个则是 \(0\),总共则有 \(2^{22}\) 种情况,直接状压。
对于一个节点 \(i\),它的答案是
此时 \(i\) 是 \(x\) 和 \(y\) 的 \(lca\)。
D 雪色雪花雪余痕
暑假训练DP2
G [联合省选 2020 A | B]信号传递
看一下 \(m\) 的范围,一眼状压。
观察,对于一个路径 \((x,y)\),它的时间消耗是这样的:
-
若 \(x \le y\),则是 \(x-y\);
-
若 \(x>y\),则是 \(k(x+y)\)。
那么对于一个点,它的答案组成分为两部分,左边和右边,我们首先预处理出一个 \(cnt\) 数组,\(cnt_{i,j}\) 表示是否有连续两个点的编号分别是 \(i,j\)。
那么我们有:
这其中,\(I,J\) 表示点 \(i,j\) 对应的信号站编号,式子的前半部分表示从 \(i\) 到 \(j\) 的路线是沿 \(x\) 轴正方向的,后半部分则相反。
可以得出:
我们令 \(val\) 表示中间那一坨,设 \(dp_S\) 表示现在第 \(i\) 位,点集选取状态为 \(x\) 的最小贡献,则有:
最终答案就是 \(\displaystyle dp_{2^m -1}\)
考虑到数组要花费 \(23\times 2^{23}=771751936 B=763MB\) 的空间,有点紧凑,我们考虑一些 奇技淫巧 来优化。
个人比较喜欢二分数组,其他 方法 网上还有很多。
C. [ABC234G] Divide a Sequence
这个还是相对友好。
我们考虑设 \(dp_i\) 表示在第 \(i\) 位时的全部贡献。
那么我们知道每一段区间都可被分成前后两部分,而某一部分又可以再分,那么我们可以列出状态转移方程:
这个算法的时间复杂度是 \(O(n^2)\),可以得到高贵的 \(0\) 分。
我们想到,对于一段单峰序列,这一段的最大值都不变,最小值同理,那么我们可以用单调栈维护那一坨,达到 \(O(n)\)。
csp-s模拟赛18
A 星际旅行
B 砍树
写了个小二分,顺便证伪了。但是有的地方还是对的,优化优化能有 70.
首先,我们观察到,对于一个 \(d\) 和 \(a_i\),它们的贡献是
这就是一个关于 \(d\) 和 \(a_i\) 的二元函数。
我们确定一个 \(a_i\),用代码生成一下这个函数的前几十项,观察规律。
观察到这个序列分成三部分,第一部分是区间 \([1,5]\),呈现离散分布;第二部分是区间 \([6,11]\),是公差为 \(2\) 的等差数列;第三部分是 \([12,+\infin]\),是一个严格单调递增序列。
那么我们针对于后两个部分,就可以采用二分答案了。
看第三部分。考虑到 \(a\) 是一个序列,有很多数,那么这个答案一定呈现单调递增的部分一定是 \([\displaystyle \max_{i=1}^{n}a_i,+\infin]\),而又考虑到答案一定不会超过 \(\displaystyle \min_{i=1}^{n}a_i+k\),那么这个二分答案的左右区间就固定下来了,因为 \(n\) 很小,每个 mid 就 \(O(n)\)ckeck 一遍。
再来看第二部分,这部分的单调区间是 \([\lceil\frac{a_i}{2}\rceil,a_i]\),和上一部分相似。
最后一部分,这一部分虽然不能二分,但是也有优化。还是考虑序列的最小值,若当前最大值已经进入离散区间,但是最小值可能仍然留存在单调区间,那么我们有这样的不等式:
在枚举的时候取个 \(\min\) 即可。
C 超级树
D 成绩单
csp-s模拟赛19
A 序列
这个搞复杂了。。
就是预处理出全部可能公比的幂,然后每个区间用 set 去重统计答案即可。
公比为 \(1\) 的情况要提前特判。
B 熟练剖分(tree)
C 建造游乐园(play)
欧拉图就是满足所有点的度都是偶数的图。
考虑答案就是欧拉图个数乘 \(C_n^2\),也就是 \(\frac{n(n-1)}{2}\),有 \(n\) 个节点的不一定联通的图的个数为 \(\displaystyle 2^{C_n^2}\),那么假设我们对前 \(n-1\) 个节点随意连边,并将所有度为奇数的点向第 \(n\) 号点连边。由于一条边会给整体增加 \(2\) 的度数,那一张图全部节点的度数和一定为偶数,又因为前 \(n-1\) 个节点的度数均为偶数,所以第 \(n\) 号节点的度也是偶数,全图的度均为偶数,总共有 \(2^{C_{n-1}^{2}}\) 种方法。
设 \(g_i\) 表示不一定联通的图个数,\(dp_i\) 表示联通图个数,那么有:
计算即可。
D 由乃的 OJ
有一个前置题目 P2114 是超级简化版。
但是整体思路是不变的,都是先找全 \(0\) 和全 \(1\) 在经过这条树链后会变成啥,然后贪心地优先选 \(0->1\) 之类的。
用一棵线段树全部维护 4 种情况。
然后要卡空间,有些要开 ull。
csp-s模拟赛21
随便打打暴力就有 rk5???
T1 T2 你 byd 两个紫?
A 法阵
这是数学题,相当于填两种颜色,类似于将上下两个同色的梯形拼起来。
分别枚举两个梯形相交的纵坐标,一个梯形的上界以及一个梯形的下界,因为 \(m\) 和 \(n\) 可以反,所以答案要乘二。
然后还要后缀和优化。
B 连通块
暴力竟然有 60 分,亏我特判链的情况写了个平衡树(map)。。。
考虑将删边的操作逆向为加边,从最后一个操作向前移动,每次维护并查集,加边就启发式合并,更新每个并查集的直径。
有个 \(union\) 函数挺牛逼的:
inline void get(int &x,int &y,int z,int w)
{
int a=x,b=y,c=dis(x,y);
if(dis(x,z)>c) a=x,b=z,c=dis(x,z);
if(dis(x,w)>c) a=x,b=w,c=dis(x,w);
if(dis(y,z)>c) a=y,b=z,c=dis(y,z);
if(dis(y,w)>c) a=y,b=w,c=dis(y,w);
if(dis(z,w)>c) a=z,b=w,c=dis(z,w);
x=a,y=b;
}
inline void un(int x,int y)
{
x=find(x),y=find(y);
if(x==y) return ;
if(sz[x]<sz[y]) swap(x,y);
get(l[x],r[x],l[y],r[y]);
fath[y]=x,sz[x]+=sz[y];
}
C 军队
D 棋盘
看一眼空间,1G,那我就放心写 \(O(np)\) 的 dp 了。。。
看一眼大样例,怎么后面几万个数字全是 \(0\)??而且样例 3 全是 \(0\)??
一下子懂了,加个卡时,多骗到 \(20\)。。。
csp-s模拟赛21
今天又是喜闻乐见的 GalGame 集合~
满穗第二次登场。
A Kanon
一个不同的暴力思路?
这个做法理论上可以卡,但是不好卡,至少我知道怎么卡,但是我卡不了。。。
考虑这样一个事情,若只有一个雪球,如果它先向左边 \(3\),又向左边 \(2\),是不是相当于直接向左边 \(5\)?
如果它继续向右边 \(7\),又向左边 \(2\),然后结束。
整体上是不是相当于先向左边 \(5\),又向右边 \(2\)?
这就说明许多移动是可以合并或忽略的。
当某个雪球走到左/右的走过的地方,可以认为没走,只有当它走到某处的当前最大值(只是从第一个操作到现在的最大值)然后转向,这才能被真正计算到。(不考虑别的雪球影响答案)
B Summer Pockets
C 空之境界
区间 dp。
D 穗
cdq 维护二维数点,然后珂朵莉树维护区间推平。