Week 2
随机跳题
CF1209E1
难度:下位青
咋不会 /ll
\(n\) 行实际上只会取 \(n\) 个最大值,最坏情况下你可以取前 \(n\) 列的最大值。那么只考虑 \(n\times n\) 的矩阵,可以暴力搜。
考虑 dp,\(f_{i,S}\) 表示考虑前 \(i\) 列,状态 \(S\) 里的行的 \(\max\) 已经确定,转移的时候考虑第 \(i\) 列确定了哪几行。朴素枚举子集就可以做到 \(O(m3^nn^2)\),注意到对于一个状态和一个列,可以预处理,就可以做到 \(O(n^22^n + m3^n)\)。优化成 \(n\times n\) 的矩阵就是 \(O(3^nn)\),可以通过。
思考:观察出可以优化成 \(n\times n\) 的矩阵,可以直接搜。当然注意到数据范围小,直接状压 dp。
CF1209E2
难度:蓝
\(O(3^nn)\) 的 dp 直接就过了。
CF558E
难度:青
注意到只有 \(26\) 个字符。考虑暴力!对于区间 \([l,r]\),依次从 \(a\) 考虑 \(z\),查询区间出现次数,然后从左到右覆盖。我们需要区间查询出现次数,区间进行覆盖,容易在 sgt 上实现。
思考:由于值域小考虑暴力。然后可以用 ds 优化就做完了。
P2824
难度:青
二分答案。将序列中 \(\ge mid\) 的数赋为 \(1\),其余为 \(0\)。如果操作完之后,第 \(p\) 个位置数是 \(1\) 就合法。然后转化为 \(01\) 排序问题。每次查询 \(0\) 出现次数 \(c\),然后 \([l,l+c-1]\) 赋值为 \(0\),其余赋成 \(1\) 就对了。
思考:妙妙 trick。
B4161
难度:绿
闹麻了。赛时会了几乎所有的观察,没想到最后的差分 /ll
你注意到一个操作 \(1\) 的贡献显然与它后面操作 \(2\) 的个数有关,容易写出这个暴力:
void sub1() {
for (ll t = q; t; -- t ) {
for (ll i = r[t]; i >= l[t]; -- i ) {
if (o[i].op == 2) ++ ct;
else
p[i] = add(p[i], fstpow(2, ct));
}
}
for (ll i = 1; i <= m; ++ i ) {
if (o[i].op == 2) continue;
A[o[i].id] = add(A[o[i].id], p[i] * o[i].v % mod);
}
for (ll i = 1; i <= n; ++ i ) cout << A[i] << ' '; cout << '\n';
}
如何加速?考虑差分。。。遇到一个 \(2\) 就翻倍,就是对的。。。
void sub1() {
for (ll t = q; t; -- t ) {
w[r[t]] = add(w[r[t]], fstpow(2, ct));
ct += s[r[t]] - s[l[t] - 1];
w[l[t] - 1] = add(w[l[t] - 1], -fstpow(2, ct));
}
for (ll i = m; i; -- i ) {
w[i] = add(w[i], w[i + 1]);
if (o[i].op == 2)
w[i] = w[i] * 2 % mod;
else
p[i] = w[i] % mod;
}
for (ll i = 1; i <= m; ++ i ) {
if (o[i].op == 2) continue;
A[o[i].id] = add(A[o[i].id], p[i] * o[i].v % mod);
}
for (ll i = 1; i <= n; ++ i ) cout << A[i] << ' '; cout << '\n';
exit(0);
}
思考:还是菜了。这个差分很显然啊!
P6847
难度:蓝
我咋暴力都不会写 /ll
树形 dp。\(f_{u,t}\) 表示考虑 \(u\) 子树,\(u\) 和它父亲的边断的时刻 \(\le t\)。转移的时候:
- \(u\) 无贡献。\(f_{u,t} = \sum f_{v,t}\)。
- \(u\) 有贡献。那么子树内如果想要贡献,断开时间需要 \(\le d_u\),\(f_{u,t} = \max(w_u + \sum f_{v,d_u})\),其中 \(t \ge d_u\)。
时间复杂度 \(O(nk)\)。可以获得 \(34\) 分。
思考:缺一个 sgt 合并优化。
P9318
难度:紫
/ll/ll/ll/ll/ll/ll
考虑 dp。\(g_i\) 表示前 \(i\) 列,高度为 \(h\) 随便填的方案数。考虑这个怎么计算,我们按每一行单独考虑,\(l_i\) 表示每一行的前 \(i\) 列,显然 \(l_0=l_1=1\),\(l_i = l_{i-1}+l_{i-2}\),即斐波那契数列。那么 \(g_i = h_i^h\)。
再考虑 \(f_i\) 表示前 \(i\) 列,高度为 \(m\),合法的方案数。正着难做考虑熔池!转移的时候枚举 \(i\) 所在的连通块,\(\displaystyle f_i = g_i - \sum_{j=1}^{i-1} f_jg_{i-j}\)。容易做到 \(O(w^2)\)。我咋这个都不会 /ll/ll/ll/ll 拼包获得 \(80\) 分。
考虑一个依赖 \(h\) 的算法来通过本题。考虑什么时候连通,你发现每一列至少有一个向右出去的 \(1\times 2\) 的块,考虑在这上边做文章。\(f_{i,j}\) 表示第 \(i\) 列有 \(j\) 个 \(1\times 2\) 的块出去了。初始化 \(f_{1,j} = \binom{h}{j}\)。转移的时候枚举第 \(i\) 列有多少个,注意保证 \(j \ge 1\)。可以考虑滚动数组优化,做到 \(O(h^2w)\)。
\(hw \le V\),常见的 trick 是转化成 \(\min(h,w) \le \sqrt V\)。考虑数据点分支,当 \(h^2 \le w\) 时,使用解法二,否则解法一。时间复杂度 \(O(\min(h^2,w)w)\)。
思考:两种 dp 做法,常见的思考是按列去 dp,转移枚举 \(i\) 所在连通块。另一种思考连通的本质,按这个去 dp。
P3411
难度:青
有点难,不会 /ll
大胆猜想答案是最长不下降子序列,但是不对。手玩一下可以发现,你对每个数离散化成排名之后,需要这个子序列里值域连续。那咋办。
定义满足要求的序列为好序列。一个好序列,由三部分组成。\([mn,mn+1)\),\([mn,mx]\),\((mx-1,mx]\),其中 \([mn,mx]\) 要求序列里所有 \(\in [mn,mx]\) 的数都出现在这个好序列里,这要求 \(\forall x < y \in [mn,mx], r_x < l_y\),其中 \(l_a,r_a\) 表示 \(a\) 最左侧 / 右侧出现的位置。那么我们按小到大枚举数,当 \(l_i > r_{i-1}\) 时就只能以 \(i-1\) 为结尾算一次。前面和后面的可以暴力。那么做完了。
思考:难难题 /ll
ABC311F
难度:绿
感觉我菜完了。容易想到 sgt 维护哈希值,具体怎么维护?
单点修改容易,问题是在 pushup 的时候,我们可以这么写:
struct node {
ll l, r;
ull hs;
} tc[N];
void pushup(ll k) {
ll mid = tc[k].l + tc[k].r >> 1;
tc[k].hs = tc[ls].hs * (pw[r - mid]) + tc[rs].hs;
}
那么做完了。
思考:考虑是怎么哈希的,\(hs_i = hs_{i-1} \times P + S_i\),可以合并。
P5268
难度:青
神了。注意到 \(n \le 5 \times 10^4\),考虑分块 / 莫队。
考虑莫队,答案是 \(\sum get(l_1,r_1,x)get(l_2,r_2,x)\),考虑熔池,那么 \(get(l,r,x) = get(1,r,x) - get(1,l-1,x)\),不妨记 \(g(p, x) = get(1,p,x)\),答案就能变成 \(\sum (g(r_1, x) - g(l_1-1,x))(g(r_2,x) - g(l_2,x)) = \sum g(r_1,x)g(r_2,x) - g(r_1,x)g(l_2,x) - g(l_1-1,x)g(r_2,x) + g(l_1-1,x)g(l_2,x)\),使用莫队维护做到 \(O(n\sqrt n)\)。
但是还是太吃操作了。考虑分块,维护 \(g_{i,j}\) 表示前 \(i\) 个数在前 \(j\) 个块里出现了多少次,容易 \(O(n\sqrt n)\)。询问的时候拆贡献,分别维护两个区间整块、散块的贡献,那么搞定了。时间复杂度 \(O((n+q)\sqrt n)\)。
P3295
难度:蓝
萌在哪?!!
容易使用并查集做到 \(O(nm)\)。具体我们对于区间 \([l_1,r_1],[l_2,r_2]\),将 \(l_1+x\) 和 \(l_2+x\) 合并掉。
这类一一对应合并的题,妙妙的 trick 是使用 st 表优化,\(f_{i,k}\) 表示 \([i,i+2^k-1]\) 区间所在集合的根的左端点,初始化 \(f_{i,k}=i\)。合并的时候倍增来跳。最后对于第 \(k\) 层,考虑如何向下合并,记 \(p = f_{i,k}\),将 \(f_{i,k-1}\) 的祖先合并到 \(p\),将 \(f_{i+2^{k-1},k-1}\) 合并到 \(p + 2^{k-1}\)。
这是因为并查集具有结合律,你假设当前要让 \([a,b]\) 和 \([c,d]\) 合并,将 \([a,b]\) 划分成 \([a,m_1]\) 和 \((m_1,b]\),将 \([c,d]\) 划分成 \([c,m_2]\) 和 \((m_2,d]\),那么你分别合并两个前者和两个后者显然是对的。
合并的代码:
for (ll p = Lg[n]; p; -- p ) {
for (ll i = 1; i + (1ll << p) - 1 <= n; ++ i ) {
ll rt = find(i, p);
merge(i, rt, p - 1);
ll ex = 1ll << (p - 1);
merge(i + ex, rt + ex, p - 1);
}
}
思考:太秒了!!!
P1989
难度:青
很好的 bitset 题。首先考虑暴力咋写,首先肯定可以 \(O(n^3)\),\(O(m^2)\),\(O(nm)\)。三个做法分别是枚举三个点,枚举两条边,枚举一个点和其对边。
前两种没有前途,考虑第三种做法,具体地,我们枚举每条边 \((u,v)\),然后考虑是否存在 \(w\) 使得存在边 \((u,w)\) 和 \((v,w)\)。
考虑使用 bitset 优化,相当于 \(u,v\) 的出边集合与起来。但是这样空间会炸。考虑根号分治,瓶颈在于有一些度数小的点,我们给它开个 \(n\) 个点,这样很多都是 \(0\)。于是设立阈值 \(B = \sqrt n\),当度数 \(> B\),开 bitset,否则直接暴力。那么做到时间复杂度 \(O(n\sqrt n)\)。
P3535
难度:绿
一个贪心的结论是编号 \(>k\) 的点之间的边是肯定会保留的。因为如果一个编号 \(\le k\) 的点,在一个简单环里,你断开这个编号 \(\le k\) 的点的出边显然是优的。那么先把保留的边加入,剩下的边能加则加即可。
思考:贪心猜结论。
P5684
难度:下位绿
萌萌计数。考虑直接熔池,然后我们只关心每个字母的出现次数,对于同一种字母,假设出现了 \(k\) 次,那两边都会出现 \(k/2\) 次,随便排列计数一下即可。
思考:简单题。
P11008
难度:绿
容易 \(O(n^2)\),可以获得 \(66\) 分?!
考虑我们暴力的过程。我们枚举 \(p_1\),显然要求 \(p_1 \ne b_1\),然后我们递推出整个 \(p\),判断 \(\max p\le n\) 且 \(\min p > 1\) 则合法,且 \(p\) 没有重复出现的数则合法。考虑怎么快速判断这个东西,对 \(b\) 做前缀和,那么 \(s_i\) 的意义就是 \(s_i = p_1 \oplus p_{i+1}\),那么 \(p_i = p_1 \oplus s_{i-1}\)。先考虑会不会出现重复数,你发现假设 \(p_x = p_y\),有 \(p_1 \oplus s_{x-1} = p_1 \oplus s_{y-1}\),容易发现与 \(p_1\) 无关。又由于题目保证有解,所以肯定不会出现重复数。
只需要判断 \(\max(p_1 \oplus s_i) \le n\) 和 \(\min(p_1\oplus s_i) \ge 1\),容易字典树随便维护。那么搞定。时间复杂度 \(O(n\log n)\)。
思考:考虑暴力。考虑异或前缀和,然后可以优化掉暴力。
P12021
难度:绿
考虑建边,将 \(x \to kx\) 建边,你发现图里是一条条链。每条链是独立的,可以分别计算然后乘起来。然后你发现在每条链上选的方案数,只与链的长度有关。考虑计算两个东西:
- 长度为 \(l\) 的链的方案数?考虑 dp,容易发现就是斐波那契数。
- 长度为 \(l\) 的链的个数?你发现长度至少为 \(i\) 的链上的点个数就是那些能被 \(k^{i-1}\) 整除的数的个数,手玩一下容易得到结论。那么搞定。
思考:想到连边,就很好计算了。
P9323
难度:青
交互只会做黄。那咋办。
由于要求与 \(0\) 方案等价,显然我们构造出来的东西是个森林(没有环)。也就是一个点至多一条出边。
你考虑怎么求点 \(i\) 的出边,显然可以只在 \([1,i-1]\) 里找。你考虑当前已经连上了 \([1,i-1]\),得到 \(i-1\) 个版本。然后你发现一个 key observation 是你可以二分第一个使得 \(1,i\) 连通的版本 \(j\),这表明原图里 \(j,i\) 连通,如果找不到显然 \(i\) 没有出边(或者是后面的点连到 \(i\))。
然后如果你朴素建完 \(n\) 个版本再连边会被卡。考虑边建版本边连边,具体考虑点 \(i\),如果 \(i\) 找到了出边 \(j\),直接用版本 \(i-1\)。否则使用任意一个版本,再与 \(i\) 连上边的新版本,具体你取最后一个二分得到的版本即可。
边数是 \(O(n\log n)\) 量级,显然远小于这个东西,能过。
思考:人类吗。
P9255
难度:绿
tang。
\(k\le 3\) 随便做。你考虑 \(k>3\):
如果满足 \(a_1\le a_2 \le \cdots \le a_n\) 显然无解。不然至少存在一个 \(i\) 满足 \(a_i > a_{i+1}\),把 \(i,i+1\) 单独分两段,剩下随便分就是对的。那么做完了。
思考:萌萌结论题。
P10102
难度:青
赛场上可能只会随机选点 /ll
要求 \(A \times B = C\),考虑压维,构造一维矩阵 \(D\),那么大概率满足 \(D \times A \times B = D \times C\),这样就是 \(O(n^2)\) 的了,多跑几遍就能过。至于这个 \(D\),随一个出来就行。
思考:trick,考虑压维。好像也能哈希。
lojp137
难度:青
kruskal 重构树 /ll 复习一下。
算法的流程是假设当前在求点对 \((x,y)\) 之间的路径上边权最大值的最小值。考虑 kruskal 的算法流程:
- 建立 \(n\) 个集合,初始点权都是 \(0\)。
- 按边权小到大排序,依次考虑每条边 \((u,v,w)\)。
- 找到 \(u,v\) 的根节点,新建一个点,点权为 \(w\),将 \(u,v\) 的根节点分别设为这个新建节点的左右儿子。
那么建立出这珂重构树之后,任意两点的答案就是它们 lca 的点权。妙完了。
P4768
难度:蓝
关心两点间路径上边权最小值的最大值,考虑 kruskal 重构树。
你建出这珂树之后,从 \(v\) 开始走,找到最后一个权值 \(> v\) 的祖先节点,那么这个节点子树里所有的叶子节点,你都能从 \(v\) 走到它,那么预处理子树内到 \(1\) 距离的最小值即可。半个钟过了,代码不算难写。
思考:想到重构树之后就随便做了。
P16318
难度:青
不会做。感觉菜完了。
由于公差为 \(1\),直接令 \(a_i \to a_i - i\),问题就变成让一段数相等。考虑二分答案 \(x\),然后考虑怎么快速判断 \([l,r]\) 是否合法。显然我们肯定让所有数变成中位数,维护区间中位数,再维护区间小于中位数的元素和,容易用对顶堆和 bit 维护,做到 \(O(n\log^2 n)\)。
但是过不去 /ll,需要优化。你发现假设区间 \([l,r]\) 合法,任意子区间也合法,考虑双指针,优化掉一个 log。
注意由于本题带删除,使用对顶可重集来维护中位数。但是注意由于带删除,直接钦定某个集合是中位数所在集合,不然可能会出错。
思考:第一步的转化是关键的。这类公差为 \(1\) 的等差序列就要想到减去 \(i\) /ll
P16314
难度:绿
感觉冷静下来认真想是容易的。但是赛时冲 t3 让我没法冷静下来 /ll
记 \(s_i\) 表示第 \(i\) 列 \(1\) 的数量,显然 \(\forall i\) 有 \(s_i + s_{m-i+1} < 2\),接下来分讨:
- \(s_i + s_{m-i+1} \le 1\) 无限制。
- \(s_i = s_{m-i+1} = 1\),记行分别是 \(x,y\),这要求行 \(x\) 与行 \(y\) 操作方向相同。
- \(s_i = 2 / s_{m-i+1}=2\),记行分别是 \(x,y\),这要求行 \(x\) 与行 \(y\) 操作方向不同。
建边,然后跑连通块即可。时间复杂度 \(O(nm)\)。
P16309
难度:紫
依旧不会树上背包 /ll
容易想到状态 \(f_{i,j}\) 表示考虑 \(i\) 的子树,\(i\) 所在连通块大小为 \(j\) 的方案数。初始 \(f_{u,1}=1\)。
然后考虑枚举 \(u\) 所在连通块大小为 \(s_u\),方案数为 \(w_u\),枚举儿子所在连通块大小为 \(s_v\),方案数为 \(w_v\)。考虑边 \((u,v)\) 是否活着:
- \((u,v)\) 活着。要求 \(s_u+s_v \le k+1\),那么 \(f_{u,s_u+s_v} \leftarrow w_uw_v\)。
- \((u,v)\) 爆掉。这要求 \(s_v = k / k+1\),\(f_{u,s_u} \leftarrow w_uw_v\)。
直接做是二次方的。但是使用哈希表来存 \(f\),有用的状态数极少,题解证明时间复杂度 \(O(n\sqrt n)\),能过。
P16356
难度:紫
考虑结论:对于任意正整数 \(x,y,z\),如果 \(2y > x\),有 \(x\bmod y + y\bmod z \ge x \bmod z\)。那么考虑一个顶点序列 \((v_{i-1},v_i)\),如果中间存在 \(p\) 使得 \(2a_p > a_{v_{i-1}}\),把 \(p\) 加入顶点序列显然不会更劣,于是令 \((v_{i-1},v_i)\) 为不合法转移。
假设当前在计算 \(f_u\),在计算完 \(f_u\) 后,前面满足 \(2a_p < a_u\) 的 \(p\) 就没用了,可以使用队列维护,将其弹掉。同时注意距离 \(\le L\) 这个限制。每次可以暴力枚举队列更新。由于队列里满足 \(a_x \ge 2a_{x+1}\),队列大小是 \(O(\log V)\) 的,每次暴力更新就是 \(O(n\log V)\)。
思考:妙妙 trick。缺一个根号分治做法。
浙公网安备 33010602011771号