上一个写的太多了,卡爆了。所以再开一个。
P4321 随机漫游
一道综合多种算法的好题。
首先按照图上随机游走的套路,再依据 \(n\) 很小的限制,可以设出 \(dp\) 方程:设 \(f_{s, u}\) 表示当前走过的点集为二进制数 \(s\),当前在 \(u\) 点,再走完所有点的期望步数。那么显然有 \(f_{(1 << n) - 1, u} = 1\)。
然后写一下转移柿子:
然后发现这是一个 \(2 ^ n \times n\) 元的线性方程组。所以可以直接高斯消元。时间复杂度 \(O((2 ^ n \times n) ^ 3)\)。但是显然爆了。
再考虑将转移方程改写一下,分 \(v\) 原本是否属于 \(s\) 进行讨论:
整理得到:
这样,只需要按照集合 \(s\) 大小倒序枚举,方程组大小自然降为 \(O(n)\)。
时间复杂度 \(O(2 ^ n n ^ 3 + Q)\)。
\(\text{AC code}\)
P3723 [AH2017/HNOI2017] 礼物
首先发现加减相对于两个手环是对称的。因此可以把对一个手环的减法转化成对另一个手环的加法。这样可以假设全是在第一个手环上执行的加减操作。
第一个手环执行了加 \(c\) 的操作,且旋转过之后的序列为 \([x_1, x_2 \cdots x_n]\),第二个手环为 \([y_1, y_2 \cdots y_n]\)。计算差异值并化简,可以得到差异值是:
\(\sum x ^ 2 + \sum y ^ 2 + c ^ 2 n + 2c(\sum x - \sum y) - 2 \sum xy\)
可以发现,这个序列只有最后一项是不定的。
因此将 \(y\) 序列翻转后再复制一倍,与 \(x\) 卷积,答案就是卷积后序列的 \(n + 1 \sim 2n\) 项系数的 \(\max\)。
直接暴力枚举 \(c\),加上前面依托就行了。
\(\text{AC code}\)
P3514 [POI2011] LIZ-Lollipop
回归简单题。
给定权值为 \(1\) 或 \(2\) 的序列,每次询问有没有权值等于 \(k\) 的子区间。
神仙思维题。
假设当前有一个序列权值和为 \(w\)。那么分下面情况讨论:
-
\(a_l = 2\),\([l + 1, r]\) 的权值和为 \(w - 2\)。
-
\(a_r = 2\),\([l, r - 1]\) 的权值和为 \(w - 2\)。
-
\(a_l = a_r = 1\),\([l + 1, r - 1]\) 的权值和为 \(w - 2\)。
综上,总能根据一个权值为 \(w\) 的序列,构造出权值为 \(w - 2\) 的序列。
因此只需要知道序列中,权值为奇数的最大子区间和权值为偶数的最大子区间就可以了。
代码下周再补。
P8060 [POI2003] Sums
同余最短路典题。
CF1527E Partition Game
首先能够想到一个很典的 dp:记 \(f_{i, j}\) 表示前 \(i\) 个元素划分成 \(j\) 段的最小代价。转移 \(O(n)\),时间 \(O(n ^ 2 k)\)。
考虑优化。首先看这个转移柿子:\(f(i, j) = \min \{ f(k, j - 1) + cost(k + 1, i) \}\),这个东西看起来就很凸性。看了看题解确实也是这样的,但是我不会证明。
接下来考虑线段树优化。首先开一个线段树,把 \(cost\) 和 \(dp\) 值加起来放到线段树里。考虑每次转移都是加入一个新元素,并且对前面的 \(dp\) 值没有影响。接下来考虑这个新加入的元素对前面 \(cost\) 值的影响。可以发现,他只对 \(\mathrm{last}_{a_j} \le i\) 的有影响。
所以只需要线段树支持区间加,区间取 \(\min\),单点插入就可以了。
CF1149C Tree Generator™
可以发现,两个点之间的距离,就是这两个点之间括号序列,消掉匹配的括号以后,剩下的没有匹配的括号。
因此问题转化为求最大权值子段,子段权值为消掉合法括号匹配之后的括号数。
这个东西可以线段树维护一下。由于最后括号序列一定形如 \(\texttt{))))...((((}\),可以记一下当前区间剩下的 \(\texttt{), (}\) 的数量分别是多少,以及区间最大权子段。转移的时候分类讨论一下即可。
CF242E XOR on Segment
这道题有许多做法,其中最简单的就是拆位建 \(20\) 棵线段树爆算。
不赘述了。依据的是亦或的位独立性。\(O(n \log n \log V)\)。
P4046 快递服务
不妨设 \(f_{i, j, k}\) 表示当前三个司机在 \(i, j, k\) 号公司,而且已经遍历到了 \(\max(i, j, k)\) 号公司的 minimum cost。
转移是平凡的。可以做到 \(O(n ^ 3)\),\(n = 1000\)。由于常数小 1s 完全不虚。
signed main() {read(n);rep(i, 1, n) rep(j, 1, n) read(d[i][j]);a[ ++ m] = 3, a[ ++ m] = 2, a[ ++ m] = 1;while (scanf("%lld", &a[ ++ m]) != EOF);memset(f, 0x3f, sizeof f); f[1][2][1] = 0;rep(i, 3, m) {rep(j, 2, i) rep(k, 1, j) f[(i & 1) ^ 1][j][k] = INF;rop(j, 2, i) rop(k, 1, j) {chkmin(f[(i & 1) ^ 1][j][k], f[i & 1][j][k] + d[a[i]][a[i + 1]]);chkmin(f[(i & 1) ^ 1][i][k], f[i & 1][j][k] + d[a[j]][a[i + 1]]);chkmin(f[(i & 1) ^ 1][i][j], f[i & 1][j][k] + d[a[k]][a[i + 1]]);}} rep(i, 1, m) rep(j, 1, m) ans = min(ans, f[m & 1][i][j]);printf("%lld\n", ans); return 0;
}
P4158 粉刷匠
发现木板的粉刷相对独立。若求出每个木板粉刷 \(j\) 次获得最大收益 \(w_{i, j}\),则容易进行分组背包。
现在考虑如何求 \(w_{i, j}\)。不妨设 \(f_{i, j}\) 表示粉刷了前 \(i\) 个格子,粉刷了 \(j\) 次获得的最大收益。枚举上一次粉刷的位置,可以得到转移
好像应该是 \(k + 1\)。不管了,大概就这样。最后背包合并就行了。\(O(nm ^ 2t + nt^2)\)。
void calc(int n) {rep(i, 0, m) rep(j, 0, t) f[i][j] = 0;rep(i, 1, m) s1[i] = s1[i - 1] + (g[n][i] == '1');rep(i, 1, m) s0[i] = s0[i - 1] + (g[n][i] == '0');rep(i, 1, m) rep(j, 1, t) rop(k, 0, i) {f[i][j] = max(f[i][j], f[k][j - 1] + s1[i] - s1[k]);f[i][j] = max(f[i][j], f[k][j - 1] + s0[i] - s0[k]);} rep(i, 0, t) rep(j, 0, m) w[n][i] = max(w[n][i], f[j][i]);
}
signed main() {read(n, m, t);rep(i, 1, n) scanf("%s", g[i] + 1);rep(i, 1, n) calc(i); memset(f, 0, sizeof f);rep(i, 1, n) rep(j, 0, t) rep(k, 0, j)f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);rep(i, 0, t) ans = max(ans, f[n][i]); printf("%lld\n", ans);
}
不妨让我们一起来考虑优化!可以发现,背包合并的物体体积是 \(O(m)\) 而不是 \(O(t)\) 级别的,因为最多染 \(m\) 次。
复杂度瓶颈变成了预处理。不妨改变一下状态,设 \(f_{i, j, k}\) 表示染了前 \(i\) 个格子,用了 \(j\) 次,最后一个染的是红 / 蓝。这样就可以做到 \(O(nmt)\) 转移。故做到 \(O(nmt)\)。