2023年10月12日 / 比赛 / 信息学
代码与题面
长春花有愉快的回忆的寓意。
题面及代码见文首下载包。
首先, O ( p 2 ) O(p^2) O(p2)的暴力不难想到,直接枚举 a a a, b b b即可。
接着,你尝试 p = 99991 p=99991 p=99991( 1 0 5 10^5 105以内最大的质数),你发现答案是 20 20 20,并不大。虽然这并没有单调性,但是你可以大胆猜测所有 p p p的答案都不会很大。如果你比较严谨,你可以打一下表,只需要几分钟时间,你会发现最大的答案为 31 31 31。那么我们枚举 a a a的时候只需要枚举到 31 31 31即可,时间复杂度近似于 O ( p ) O(p) O(p)。
紫罗兰的花语寓意为永久的爱。
注意到是无向无权图,每次断开一条边,然后从一个端点到另一个做BFS
,找到最短路径并统计方案数。判断是否为最小环,如果是统计答案。
实现上,对于松弛操作,如果成功松弛,那么重新赋值方案数;如果花费相同,那么加上方案数即可。
更多的,我们每个环可能被扫描多次,所以最后要去重。当然如果我们在输入的过程中,在加入每一条边前都做一次BFS
,那么我们就不会重复统计。(我就是这个做法)
天竺葵花语是幸福在身边。
先看 O ( n 2 ) O(n^2) O(n2)暴力,设 f [ i ] [ j ] f[i][j] f[i][j]表示考虑到第 i i i位选出了 j j j位,结尾数的最小值。
转移为: f [ i ] [ j ] = m i n ( f [ i − 1 ] [ j ] , a [ i ] ( f [ i − 1 ] [ j − 1 ] ∗ b [ j − 1 ] < a [ i ] ) ) f[i][j]=min(f[i-1][j],a[i]\ (f[i-1][j-1]*b[j-1]f[i][j]=min(f[i−1][j],a[i] (f[i−1][j−1]∗b[j−1]<a[i]) )
想必大家都做过导弹拦截(洛谷),很容易发现 f [ i ] f[i] f[i]单调递增。
利用这个性质,我们在转移 f [ i ] f[i] f[i]的过程中,我们通过二分查找找到最大的 k k k使得 f [ i − 1 ] [ k ] < a [ i ] f[i-1][k]f[i−1][k]<a[i],看看这个 k k k的性质。
如果我们去掉第一维,那么只有, j = k + 1 j=k+1 j=k+1时需要转移。时间复杂度降低至 O ( n log n ) O(n\log n) O(nlogn)
白色风信子代表纯真的爱情。
对于询问,我们注意到 ∑ k \sum k ∑k不大,我们考虑将所有的可能以某种形式保存,放入优先队列中,我们最多只需要取 ∑ k \sum k ∑k次,时间复杂度为 O ( ∑ k log ∑ k ) O(\sum k \log \sum k) O(∑klog∑k)。
关键是如何保存,如何取值?对于一个最初的询问 [ L , R ] [L,R] [L,R],其限制 L ≤ l ≤ r ≤ R L \leq l \leq r \leq R L≤l≤r≤R这等价于 L ≤ l ≤ R , L ≤ r ≤ R , l ≤ r L \leq l \leq R,L \leq r \leq R,l \leq r L≤l≤R,L≤r≤R,l≤r,我们考虑用线段树维护最大的 a [ l ] − a [ r ] a[l]-a[r] a[l]−a[r],这要求我们取区间最大值,最小值,答案(以及位置),懒标记(有区间修改的操作)等。
Node operator +(const Node x,const Node y)
{
Node z;
z.l=x.l;
z.r=y.r;
z.z=0;
if(x.x>y.x) z.x=x.x,z.a=x.a;
else z.x=y.x,z.a=y.a;
if(x.y<y.y) z.y=x.y,z.b=x.b;
else z.y=y.y,z.b=y.b;
z.v=x.x-y.y;
z.p=x.a;
z.q=y.b;
if(x.v>z.v) z.v=x.v,z.p=x.p,z.q=x.q;
if(y.v>z.v) z.v=y.v,z.p=y.p,z.q=y.q;
return z;
}
这是合并操作(重载为加法,结构体为线段树结构体)。变量含义如下:
变量 | 含义 |
---|---|
l | 左端点 |
r | 右端点 |
x | 最大值 |
y | 最小值 |
a | 最大值下标 |
b | 最小值下标 |
v | 答案(即 a [ l ] − a [ r ] a[l]-a[r] a[l]−a[r]的最大值) |
p | 答案中最大值下标 |
q | 答案中最小值下标 |
其余的就是常规的区修区查线段树了(详细请见文首链接代码中的结构体Sgt
),时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)。
解决线段树后,我们开始考虑查询区间问题。为了方便表示,我们将要求为 L 0 ≤ l ≤ R 0 , L 1 ≤ r ≤ R 1 , l ≤ r L_0 \leq l \leq R_0,L_1 \leq r \leq R_1,l \leq r L0≤l≤R0,L1≤r≤R1,l≤r的区间表示为 ( L 0 , R 0 , L 1 , R 1 ) (L_0,R_0,L_1,R_1) (L0,R0,L1,R1)
因为取过的 l l l, r r r不能再取,所以我们需要分裂区间。但是我们需要分裂成什么样的区间,这就取决于什么样的区间好查询了。
如果区间为 ( L 0 , R 0 , L 1 , R 1 ) (L_0,R_0,L_1,R_1) (L0,R0,L1,R1),那么以下两种区间是好查询的。
代码中的Add
函数就是查询值的操作。
对于分裂操作,我们设最优解为 p p p, q q q(最小值与最大值下标)。
对于第一类区间我们将其分裂成(因为 L 0 = L 1 L_0=L_1 L0=L1统一简写成 L L L, R R R同理。):
对于第二类区间我们将其分裂成:
(p,p,L1,q−1) (L1<q)
(为了防止笔误带来的影响,可以结合代码)
最后记得重载区间比较函数。