0%

3 月 23 日 AtCoder Grand Contest 032 比赛链接:https://atcoder.jp/contests/agc032

B - Balanced Neighbors

思路: 当 $n$ 为偶数时,可以把点 $(i,n-i+1)$ 配对,然后每个点向与它一对之外的所有点连边;当 $n$ 为奇数时,把点 $(i,n-i)$ 配对,同样每一点向与它一对之外的所有点连边(包括 $n$ )。

代码链接

C - Three Circuits

思路:

因为是连通图,你可以把三个环拼成一条欧拉回路,因此如果这张图含有奇度数的点,则一定不存在构造方案。如果有一个点的度数大于等于 $6$,代表着这条欧拉回路经过了这个点至少 $3$ 次,而每一次都可以形成一个环,所以这样是一定存在构造方案的。

经过上面讨论后,剩下的图只有度数为 $4$ 和度数为 $2$ 的点。如果一张图度数为 $4$ 的点大于等于 $3$ 个,那么所有构成环的情况分为下面两种: 存在一条路径从一个度数为 $4$ 的点出发,不经过其他度数为 $4$ 的点并且回到出发点;一条路径经过两个度数为 $4$ 的点一次,并形成了环。可以发现这样的图至少存在 $3$ 个环。

剩下的情况中如果度数为 $4$ 的点少于两个,那么显然是不存在构造方案的。如果度数为 $4$ 的点恰有两个,就只有下图两种情况,只有这张图的最小割为 $2$ 时才存在构造方案。所以可以用一个 BFS,模拟一下最大流的过程判断这张图的割。

AtCoder Grand Contest 032 解题报告 1

代码链接

D - Rotation Sort

思路:

首先转换一下题意,题目中的一次操作可以看成:从序列中取出一个元素,将它插入前面或后面的一个位置,分别花费 $A$ 或 $B$ 的代价。不难发现每一个元素最多会被操作一次,且有些元素不会被操作。我们就可以从这入手设计 DP 状态。设 $f_{i,j}$ 表示处理完前 $i$ 个数且最后一不会被操作的数为 $j$ 所花费的最小代价,然后根据题意简单地转移一下就好。

代码链接

E - Modulo Pairing

思路:

假设 $lim$ 已知,考虑对于数 $x$,我们要找到一个 $y$ 与它配对,使得 $x+y$ 在模 $m$ 意义下不超过 $lim$。 如果 $x+y < m$,那么 $y$ 最大可以取 $lim-x-1$ ;如果 $x+y > m$,那么 $y$ 最大可以取 $lim+m-x$。我们让 $y$ 在对应的取值范围内选尽量大的数一定不会更劣,所以假如对于一个 $x$,我们决定了与它配对的 $y$ 与 $m-x$ 的关系,那么 $y$ 的值我们也可以确定。

考虑这样一个结论:将所有数排好序后,设最大值为 $x{max}$,如果一个数 $x$ 大于 $m-x{max}$,那么与它配对的 $y$ 就一定比 $m-x$ 大; 否则一定比 $m-x$ 小。这样以来我们就可以二分出一个位置 $pos$,在 $pos$ 左边的数两两配对小于 $m$,在 $pos$ 右边的数两两配对不小于 $m$。确定出 $pos$ 后,答案就可以跟着确定下来。

对于结论的正确性,我们可以对所有情况进行讨论。设蓝色的线为小于 $m$ 的匹配,红色的线为不小于 $m$ 的匹配,根据下图可以发现该结论对于所有情况均成立:

AtCoder Grand Contest 032 解题报告 2

代码链接

问题描述

给定一棵 $n$ 个点的有根树,其中 $1$ 号点是根节点。每个节点都被染上了某一种颜色,其中第 $i$ 个节点的颜色为 $c_i$。定义 $dep_i$ 为 $i$ 节点与根节点的距离,为了方便起见,你可以认为树上相邻的两个点之间的距离为 $1$。

站在这棵色彩斑斓的树前面,你将面临 $m$ 个问题。每个问题包含两个整数 $x$ 和 $d$,表示询问 $x$ 子树里且 $dep$ 不超过 $dep_x+d$ 的所有点中出现了多少种本质不同的颜色。

输入

第一行包含一个正整数 $T(1<=T<=500)$,表示测试数据的组数。
每组数据中,第一行包含两个正整数 $n$ 和 $m (1 \leq n,m \leq 100000)$,表示节点数和询问数。
第二行包含 $n$ 个正整数,其中第 $i$ 个数为 $ci (1 \leq c_i \leq n)$,分别表示每个节点的颜色。
第三行包含 $n-1$ 个正整数,其中第 $i$ 个数为 $f
{i+1} (1 \leq f_i<i)$,表示节点 $i+1$ 的父亲节点的编号。
接下来 $m$ 行,每行两个整数 $x(1 \leq x \leq n)$ 和 $d(0 \leq d \leq n)$,依次表示每个询问。
输入数据经过了加密,对于每个询问,如果你读入了 $x$ 和 $d$,那么真实的 $x$ 和 $d$ 分别是 $x \oplus last$ 和 $d \oplus last$,其中 $last$ 上一次询问的答案。
输入数据保证 $n$ 和 $m$ 的总和不超过 $500000$。

输出

对于每个询问输出一行一个整数,即答案。

思路

方法一: 如果这个问题在序列上且离线的话,可以发现这就是「HH的项链」。其中一个比较好的思路就是先将询问排序,从左到右扫一遍,我们将每种颜色的贡献算在这种颜色最右边的点上,用树状数组维护区间和即可。强制在线怎么办?可持久化一下,每个右端点都建一个线段树即可。

把这个放在树上也是一样,按深度从大到小考虑,我们将每种颜色的贡献算在这种颜色最浅的点上,用一个以深度为下标的线段树计算子树的答案,然后从下到上线段树合并就好。同时再开一个以颜色为下标的线段树维护深度,同样从下向上合并,合并时把较深的一点贡献在计算答案的线段树上删去。强制在线怎么办?将线段树合并的过程可持久化一下就好。空间复杂度似乎是多一个 $\log n$ 的。

方法二: 类似的从序列上的问题考虑。还有一种想法就是,从左到右扫一遍,每当加入一个颜色的点时,加上这个点的贡献,并删去这个颜色上一个点的贡献,同样用树状数组维护区间和就好。

如果把这个放在树上,就是按深度从小到大考虑,每增加一个颜色的点时,就加上这个点的贡献,并在它和与它颜色相同且相邻的点的 LCA 上删去贡献(这里的相邻是指 dfs 序上两点之间没有其他相同颜色的点)。因为一棵子树的 DFS 是连续一段,这样就可以按深度从小到大扫,用线段树维护子树区间和即可。强制在线怎么办?可持久化,每个深度都建一个线段树就好。

代码

嘴巴选手,懒,不想写代码

第一类斯特林数

定义:

表示 $n$ 个不同元素形成 $m$ 个圆排列的方案数,记为 $\begin{bmatrix} n \ m \end{bmatrix}$。

性质:

首先考虑这个怎么求,可以枚举第 $n$ 个元素,它可以自己形成一个新的环,方案数为 $\begin{bmatrix} n-1 \ m-1 \end{bmatrix}$;它也可以放在已有的 $n-1$ 个元素中一个的后面,方案数为 $(n-1)\begin{bmatrix} n-1 \ m \end{bmatrix}$,从而得出递推式:

我们设 $f_n(x)$ 为 $\begin{bmatrix} n \ x \end{bmatrix}$ 的生成函数。根据上面式子可以写出它的生成函数

而这个可以直接套用分治 FFT 在 $O ( n \log^2 n )$ 内求出,但我们还可以在它的基础上优化。考虑你目前分治到区间 $(l,r)$,在这之前已经计算出了区间 $(l, mid)$ 的卷积,这个时候应该要递归的计算 $(mid+1, r)$ 的卷积,但其实发现如果你设 $(l, mid)$ 的卷积为 $g(x)$,那么 $(mid+1,r)$ 的卷积就是 $g(x+mid)$。将式子如下化简:

你发现后面的那一块有点像卷积的形式,将其中一项反转后就可以卷起来了,这样我们做到 $O(n \log n)$ 的复杂度

Read more »

3 月 8 日 Codeforces Round #545 比赛链接:https://codeforces.com/contest/1138

E - Museums Tour

思路:

首先考虑将每一个 museum 按天数分为 $d$ 个点,然后对应的向相邻的连边。接着就很自然的想到用 tarjan 缩点,然后考虑如何统计答案。有一个性质就是如果一个 museum 对应不同天的点之间有边,那么这些点一定是在同一个强联通分量里面的,因此你缩点后直接找一个包含 museum 数最多的链就是对的。

代码链接

F - Cooperative Game

思路: 如果你知道 pollard-rho 中的 floyd 判环的话,这题的思路就应该不难想到。如果你直接模拟这个过程:让一个人每次走一步另一个人每次走两步,就能够在 $2*(t+c)$ 次交互内让两个人在环上相遇。那么问题就在于如何不知道终点位置的情况下让所有人走到终点。这里有个非常巧妙的性质就是在相遇点走 $t$ 步后一定能到达终点,如果我们设两个人分别走了 $t+x$,$2t+2x$ 步后相遇,那么 $t+x$ 一定是环长的倍数,如果再走 $t$ 步就意味着在环上总共走了 $t+x$ 步(一开始在链上走了 $t$ 步),那么就一定走到了终点。因此只要让一些人留在起点,两个人在环上相遇后所有一起走若干步,如果大家全部相遇,就意味着已经到终点了。

代码链接

问题描述

给定长度为 $n$ 的序列:$a_1,a_2,…,a_n$,记为 $a[1:n]$。有 $q$ 个询问,每个询问给定两个数 $l,r$,求 $a[l:r]$ 中所有子序列的最小值之和。

输入

第一行包含两个整数 $n$ 和 $q$,分别代表序列长度和询问数。

接下来一行,包含 $n$ 个整数,第 $i$ 个整数为 $a_i$。

接下来 $q$ 行,每行包含两个整数 $l$ 和 $r$,代表一次询问。

输出

对于每次询问,输出一行,代表询问的答案。

思路

有一种比较直观的方法就是使用莫队,队中维护一个单调栈,并且记录栈中元素到前一个元素这段区间的答案。当插入元素时,更新单调栈,通过记录的信息可以比较容易的求出答案。

这题还有一个很妙的 Idea。考虑类似于上面莫队的方法,先处理出右端点为 $i$ 的所有 $a[l,i]$ 的最小值的和,记为 $pre_i$,再处理一个 $pre_i$ 的前缀和 $spre_i$,这样 $spre_i$ 即是 $a[1,i]$ 的答案。类似的再处理出 $suf_i$,表示所有左端点为 $i$ 的序列的最小值之和,以及 $ssuf_i$ 表示 $suf_i$ 的后缀和。

预处理完这些东西后,考虑如何回答区间 $a[l,r]$ 的答案,我们设 $a[l,r]$ 中最小值位于 $pos$,这个可以利用 ST 表求出。经过 $pos$ 这一点的所有区间对答案的贡献可以直接计算出来,考虑剩下 $a[l,pos-1]$ 及 $a[pos+1,r]$ 这两段的贡献如何计算。我们发现 $spre[r]-spre[pos]$ 就包含了 $a[pos+1,r]$ 这一段的答案和右端点在 $[pos+1,r]$ 左端点在 $[1,pos-1]$ 的区间最小值之和。而后面一部分由于跨过了 $pos$,它的贡献可以等价为 $(r-pos)*pre[pos]$,将它们相减就可以计算出 $a[pos+1,r]$ 的贡献,类似的可以同样计算出 $a[l,pos-1]$ 的贡献。

这样我们可以在 $O(1)$ 的时间内回答每一个询问,而算法复杂度的瓶颈在 $O(n \log n)$ 的 ST 表预处理上,而如果使用笛卡尔树或者用一些 $O(n)$ RMQ 的高级姿势,就可以在线性复杂度内完成这个操作。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <set>
#define MAXN 100005
#define P 998244353
#define INF 0x3f3f3f3f
#define rint register int
#define LL long long
#define LD long double
using namespace std;

int n, q, top, a[MAXN], f[MAXN][20], sta[MAXN], Log[MAXN];
LL pre[MAXN], spre[MAXN], suf[MAXN], ssuf[MAXN];

int query(int x, int y)
{
int k=Log[y-x+1];
if(a[f[x][k]]<a[f[y-(1<<k)+1][k]]) return f[x][k];
else return f[y-(1<<k)+1][k];
}

int main()
{
scanf("%d%d", &n, &q);
for(rint i=1; i<=n; ++i)
{
scanf("%d", &a[i]);
f[i][0]=i;
}
for(rint i=1; i<20; ++i)
for(rint j=1; j+(1<<i)-1<=n; ++j)
{
if(a[f[j][i-1]]<a[f[j+(1<<i-1)][i-1]]) f[j][i]=f[j][i-1];
else f[j][i]=f[j+(1<<i-1)][i-1];
}
for(rint i=2; i<=n; ++i) Log[i]=Log[i/2]+1;
sta[top=0]=0;
for(rint i=1; i<=n; ++i)
{
while(top && a[sta[top]]>=a[i]) top--;
pre[i]=pre[sta[top]]+1LL*a[i]*(i-sta[top]);
sta[++top]=i;
spre[i]=spre[i-1]+pre[i];
}
sta[top=0]=n+1;
for(rint i=n; i>=1; --i)
{
while(top && a[sta[top]]>=a[i]) top--;
suf[i]=suf[sta[top]]+1LL*(sta[top]-i)*a[i];
sta[++top]=i;
ssuf[i]=ssuf[i+1]+suf[i];
}
for(rint i=1, l, r; i<=q; ++i)
{
LL ans=0;
scanf("%d%d", &l, &r);
int pos=query(l, r);
ans+=1LL*(pos-l+1)*(r-pos+1)*a[pos];
ans+=ssuf[l]-ssuf[pos]-1LL*(pos-l)*suf[pos];
ans+=spre[r]-spre[pos]-1LL*(r-pos)*pre[pos];
printf("%lld\n", ans);
}
return 0;
}