杂题选做
P6323
不会做黄题。水平退化成啥了。
这类与全排列相关的 dp 题。常见的套路是从小到大填数。本题中考虑 \(f_{i,j}\) 表示填了 \(1 \sim i\),逆序对数为 \(j\)。
考虑第 \(i\) 个数插在哪里以及对应的贡献。此时序列中的数只有小于 \(i\) 和大于 \(i\) 两种情况。当前已经插了 \(i-1\) 个数,你会发现只有 \(i\) 个位置是需要考虑的,分别对应了造了 \(0 \sim i-1\) 个逆序对。那么这样转移就做到 \(O(n^2c)\) 了。获得 \(64\) 分。
注意到我们每次都在固定 \((i,j)\) 然后去枚举 \(k\),然后这个 \(k\) 的值域是连续的,于是考虑前缀和优化。这样变成 \(O(nc)\)。滚动一下数组就过了。
P2401
有实力的。
这题与逆序对那题不同的是,那题求逆序对,我们只关心元素的相对顺序。本题中难以直接计算增加的 < 个数,需要分讨。分讨第 \(i\) 个元素插在哪:
- 序列最开头。出现 \(i > S_1\),无贡献。
- 序列最末尾。出现 \(S_n < i\),贡献加一。
- \(S_l < S_r\) 之间。出现 \(S_l < i > S_r\),无贡献。
- \(S_l > S_r\) 之间。出现 \(S_l < i > S_r\),贡献加一。
综上,对于一个 \(f_{i,j}\) 表示 \(i\) 个元素造了 \(j\) 个 <:
- 假设第 \(i\) 个元素多造了一个
<,能插的位置就是>的位置加上序列末尾,即 \(i-2-(j-1) + 1\),其中 \(i-2\) 表示符号个数,\(j-1\) 表示<个数。那么 \(f_{i,j} = f_{i,j} + f_{i-1,j-1} \times (i-j)\)。 - 假设没造。能插的位置就是
<的位置加上序列开头,即 \(j + 1\)。\(f_{i,j} = f_{i,j} + f_{i-1,j} \times (j+1)\)。
做完了。时间复杂度 \(O(n^2)\)。
ABC461E
比较板的 ds 题。
考虑操作 \(1\),横着涂一行黑的会改变什么。如果是第一次涂这一行,贡献显然就是 \(n\)。但是如果不是呢?不妨记上一次涂这一行的时刻为 \(j\),当前时刻为 \(i\)。那么时刻 \(j \sim i\) 中有多少列被涂白了,这一次操作就会把它们变成黑的。贡献就是这个列数。
那么问题转化为求时刻 \(j \sim i\) 中,有多少列被涂白了,注意要去重。操作 \(2\) 的贡献也是同理的。\(j\) 是很好维护的,维护第 \(p\) 行,第 \(q\) 列上一次涂的时间 \(r_{p},c_{q}\) 即可。那么可以把询问序列离线下来然后扫描线用树状数组算。
不过这样太麻烦了。考虑一个更聪明的做法。我们已经维护了 \(r_p,c_q\),那么时刻 $j\sim i $ 中,有多少列被涂白了实际上就是求有多少个 \(c_q > r_p\),那么树状数组容易维护。时间复杂度 \(O(q\log n)\)。
P3531
如果没有重复的东西显然就是逆序对。然后考虑重复的字母,容易发现我们从左到右赋值做逆序对是对的。这是因为假设 S = AABA T = ABAA,那么 \(S\) 的第一个 \(A\) 一定是与 \(T\) 的第一个 \(A\) 配对的,以此类推。那么赋值完直接做逆序对就是对的。
NOIP 计划 R17 B
大意:给定一个长度为 \(n\) 的序列 \(a\),你最少需要多少次“交换相邻两项”的操作,才能满足这个序列先(非严格)增后(非严格)减。
很 cf 的题目。考虑 \(n \le 10\) 的子任务,枚举全排列,那么问题就变成怎么从枚举的序列变成序列。这个不就是 P3531。做完了。
考虑排列的子任务。这个神秘的限制有个很好的转化,就是从大到小插数,每次往左或往右插,这样就可以构造出一个合法序列。然后考虑代价怎么算,假设当前的数为 \(x\),如果往左边插的话,原序列 \(x\) 左边 \(> x\) 的数就要被交换,代价就是这个数的个数。往右边插是同理的。
感觉是上位绿。
NOIP 计划 R17 C
大意:对于一个元素介于 \([0,2^m)\) 且互不相同、长度为 \(n\) 的序列 \(a_1,a_2,\ldots,a_n\),定义它的特征序列为 \(p_0,p_1,\ldots,p_{2^m-1}\),其中 \(p_i\) 表示使得 \(a_{p_i}\) 与 \(i\) 的异或值最大的下标。给定一个特征序列 \(p\),求有多少个满足要求的原序列 \(a\) 可以得到这个特征序列。答案对 \(10^9+7\) 取模。
咕咕咕。
P17089
不会。神了。
考虑 \(w_i \le 500\) 咋做。首先显然按 \(w\) 排序,\(f_{i,j}\) 表示前 \(i\) 个球,体力剩 \(j\)。你发现这样不太好转移,引入 \(k\) 表示前面选了的球到的最远的位置为 \(k\)。
在对 \(i\) 进行转移的时候,考虑 \(k\) 的取值:
- \(k \ge w_i\),\(f_{i,j,k+1} = \max(f_{i-1,j-x_i,k}+k+1, f_{i-1,j,k+1})\)
- \(k < w_i\),\(f_{i,j,w_i} = \max(f_{i-1,j-x_i,k}+w_i,f_{i-1,j,w_i})\)
那么时间复杂度 \(O(n^3)\),可以滚掉一维。
然后考虑正解。这里有很多做法,比较容易得就是注意到有用的 \(w\) 的数量是 \(O(n)\) 量级的,离散化即可。这个可以这样证明:
考虑全部球都扔出去的情况,记此时球 \(i\) 所在位置为 \(t_i\),显然 \(t_i = \max(t_{i-1} + 1,w_i)\)。那么此时 \([w_i,t_i]\) 这段区间一定都是有球在的,此时没有球在的位置一定也不会有球了。于是记录离散化 \(t_i\) 即可。这样就把所有可能出现球的位置离散化完了。那么做完了。
mem(f, -0x3f);
f[0][0][0] = 0;
for (ll i = 1; i <= n; ++ i ) {
for (ll j = 0; j <= s; ++ j )
for (ll k = 0; k <= L; ++ k )
f[i & 1][j][k] = f[!(i & 1)][j][k];
ll h = Mp[B[i].w];
for (ll k = 0; k <= L; ++ k ) {
ll g = Mp[to[k] + 1];
for (ll j = B[i].X; j <= s; ++ j ) {
if (to[k] >= B[i].w)
f[i & 1][j][g] = max(f[i & 1][j][g],
f[!(i & 1)][j - B[i].X][k] + to[k] + 1);
else
f[i & 1][j][h] = max(f[i & 1][j][h],
f[!(i & 1)][j - B[i].X][k] + B[i].w);
}
}
}
P16131
首先考虑把复合串拆开,由于没有哪个初始字符串是另一个初始字符串的前缀,这一步容易用哈希实现。
那么得到一个长度为 \(k\) 的序列 \(p\),元素之间互不相同且由 \(1\sim n\) 组成。问题转化成求 \(p\) 的排名。
比较经典的 trick 是转化成求字典序比 \(p\) 小的序列数,这样可以按位来填。
考虑第 \(i\) 位,此时前 \(i-1\) 位都是被钦定的。记此时 \(n\) 个数里还没被钦定的,且 \(< p_i\) 的数有 \(f\) 个,那么如果在第 \(i\) 位填上 \(f\) 个数里的某一个,第 \(i+1 \sim k\) 位可以任意填,由于此时 \(n\) 个数里已经选了 \(i\) 个,方案数就是 \(f \times \binom{n-i}{k-i} \times (k-i)!\)。那么钦定第 \(i\) 位为 \(p_i\) 继续往下填即可。
\(f\) 的计算可以用树状数组或者 set 维护。时间复杂度 \(\mathcal{O(k\log n)}\)。注意由于我们求的是比 \(p\) 小的序列数,答案要 \(+1\),这里也要取模。赛时因为这个 \(100 \to 98\)。
P15861
不会这个题还有救吗。
考虑 \(m = n(n-1)/2\),此时显然直接放 \(n\) 个相同元素是对的。考虑微调,记相同元素为 \(p\)。找到最大的 \(n_0\) 使 \(n_0(n_0 - 1)/2 < m\),记 \(t = m - n_0(n_0 - 1)/2\),要有 \(t\) 的贡献,只需要在第 \(t\) 个 \(p\) 后插入一个 \(2p\) 就好了。那么做完了。注意特判 \(m=0\)。
P4462
做一个异或前缀和。可以发现就是求满足 \(i,j\in [L-1,R]\),\(s_i \oplus s_j = k\) 的 \((i,j)\) 对数,用莫队维护即可。时间复杂度 \(O(n\sqrt n)\)。
P17161
进场了。
预处理出每个数的最小质因数。查表得知值域范围内 \(\omega(n) = 8\)。那么求质因数就是 \(O(\omega(n))\) 的。时间复杂度 \(O(T\omega(n))\),可能带个 \(\log\)。
P2619
直接求一遍 mst,那么得到一个 \(k\) 条白边的 mst。分讨 \(k\) 和 \(need\) 的大小:
- \(k = need\)。请输入文本。
- \(k < need\)。说明白边的边权都较大,考虑给这些边权同时减去一个值 \(w\),这样选择的白边数量只会变大。然后再求一遍 mst 得到 \(k'\),重复过程。
- \(k > need\)。说明白边边权都较小,给边权同时加上一个值 \(w\),选择的白边数量会变小。再求 mst 得到 \(k'\)。
于是直接二分 \(w\),注意相同边权要优先考虑白边。但是是可能存在一种情况找不到 \(k = need\),此时取 \(w+1\) 时 \(k = need - 1\),取 \(w-1\) 时 \(k = need +1\),这是因为存在某条黑边与新的白边边权相同,由于题目保证有解,直接用这些黑边替换掉就是对的。于是我们在 \(k \ge need\) 时记录答案 \(mst - need \times mid\) 即可。
代码:
ll l = -100, r = 100, Res;
while (l <= r) {
ll mid = l + r >> 1;
ll k2 = chk(mid);
if (k2 >= k) {
Res = mst - k * mid;
l = mid + 1;
}
else r = mid - 1;
}
cout << Res << '\n';
P13984
分块做法。我们维护 \(f_{i,j}\) 表示块 \(i\sim j\) 的最小众数,然后维护 \(g_{i,j}\) 表示数 \(i\) 在块 \(j\) 出现的个数。然后查询的时候,你先把整块的最小众数拿出来,然后考虑散块可能出现最小众数,暴力枚举即可。由于散块的贡献只有 \(\sqrt n\) 个,时间复杂度还是 \(O(n\sqrt n)\)。
浙公网安备 33010602011771号