2021.4.18 - 4.24 一周小结

这周做的题目,难度总体都挺高的,集中在 CF 上 3000 左右。并且在通过之前没有看任何一题的题解!(得瑟一下!)其实,有些难题,多想想,还是能艹过去的!Try a Try, AC is OK.(bushi)至于为什么看这些 CF 评级比较高的题呢?因为感觉评分高一些的题,在题目风格上更加 OI 一些,也更是我所擅长的。

CF1408I. Bitwise Magic

题意:

给定 \(n,k,c\),以及长度为 \(n\) 的序列 \(a\)(保证元素互不相同)。

操作 \(k\) 次,每次随机选择一个 \(a_i\),然后将其 \(-1\)。对于 \(x=0,1~...~2^c-1\) 输出最后序列的异或和为 \(x\) 的概率,答案对 \(998244353\) 取模。

\(k,c\le 16,a_i\in [k,2^c),n\le 2^c-k\)

题解:

洛谷题解区里的做法充斥着生成函数,的确看到这题想到的第一个想法就是二元生成函数:设 \(x^ay^b\times x^cy^d=x^{a+c}y^{b\wedge d}\),答案的 PGF 是 \(\sum\sum \frac 1{a!}x^ay^b\):于是问题就可以转化成快速求多个二元多项式的乘积。但是这么想是没有必要的,我有一个更加巧妙而简洁的做法:

设一种最终局面中,第 \(i\) 个数被操作了 \(d_i\) 次;那么对于一个可能的局面 \(d\{\}\),它出现的可能性可以用可重排列计算:

 


\[P(d\{\})=k!\cdot\left(\prod_{i=1}^nd_i!\right)^{-1}\cdot \left(\frac 1n\right)^k \]


 

于是,设 \(f_{i,j,S}\) 表示前 \(i\) 个数、操作了 \(j\) 次、影响为 \(S\) 的所有方案的当前阶乘倒数乘积的和,其中 \(S=\sum\limits_{p=1}^ia_i\wedge(a_i-d_i)\)。于是我们就有了一个 \(2^c\times 2^c\times k^2\) 的做法。考虑如何加速。

我们注意到两个事情:1.复杂度实际上是 \(n\times |S|\times k^2\);2.每个数等价,所以不按顺序统计不影响答案。

所以我们可以把东西序列分成若干段,在每个段内 \(S\) 的值域很小,段内可以快速 DP;最后再想办法拼起来。

由于 \(k\le 16\),所以几乎只会影响到最后的 4 位;考虑到减法退位的情况,最多再将前面的某一位开始全部取反;所以我们可以按照它影响的是前面的哪一位分段,每一段内的 DP 数组中,\(S\) 的取值个数都只有 \(15\times 2\) 个,可以快速地 DP,这一部分的总复杂度就是 \(2k\times n\times k^2\)。

然后考虑如何把这 \(9\) 个 DP 数组拼起来,第一维暴力卷积,第二维 fwt 即可,复杂度 \((c-k)\times 2^c\times c\times k^2\)。

**评测链接:AC submission:https://codeforces.ml/contest/1408/submission/113971716. **

CF1474F 1 2 3 4 ...

题意:

给定一个序列,求其中最长严格上升子序列长度及其个数。

序列按如下方式给出:给定 \(n(1\leq n\leq 50)\) 和序列中的第一个数 \(x(-10^9\leq x\leq 10^9)\),接下来 \(n\) 个数 \(d_i(-10^9\leq d_i\leq 10^9)\),若 \(d_i\ge 0\),则重复 \(d_i\) 次,在序列末尾加上当前序列最后一个数 \(+1\) 的值,若 \(d_i<0\),重复 \(-d_i\) 次,在序列末尾加上当前序列最后一个数 \(-1\) 的值。

题解:

假如这个像过山车一样的序列总长只有 \(10^5\) 该怎么做?考虑增量构造,设 \(f_i\) 表示当前序列中,结尾是 \(i\) 的最长上升子序列个数;那么增加一个数 \(x\) 也就等价于执行 \(f_{x}+=\max(f_{x-1},1)\);依次加入数就行了,最后的答案只要统计那些 \(\sum\limits_{j=1}^i d_j-\min\limits_{k=1}^{i-1}\{\sum_{j=1}^kd_j\}=max\) 的 \(i\) 位置的值的和就行了(注意这个 \(f\) 是在 \(i\) 位置时的 \(f\));注意每次到达新的最低点时要将 \(f\) 数组清空。

现在考虑序列可能很长的情况。我们发现:下降操作,大致相当于把 \([pos_i,pos_{i-1}]\) 这一段平移一下加到自己身上;上升操作,相当于把区间的第一个数加上一个数,然后把这个区间前缀和一下。

有没有合适的结构可以用来维护这个 DP 数组呢?有,那就是多项式!先给出一个结论:任一时刻的 \(f\) 数组皆可以被分成 \(O(n)\) 段,每一段内的 \(f_x\) 均可以用次数小于 \(O(n)\) 的多项式 \(f(x)\) 表示。下面大致口胡一下:

  1. 序列 1,1,1,... 可以用 \(f(x)=1\) 表示。
  2. 它的前缀和可以用 \(f(x)=\frac{(1+x)x}2\) 表示,它的前缀和的前缀和可以用 \(f(x)=\frac{(2x+1)(x+1)x}6\) 表示。...... 它的任意一阶前缀和都可以用多项式表示。
  3. 下降操作中,除了区间第一个数之外的 \(f\) 可以用 \(f(x)+f(x-1)\) 表示;第一个数可以单独拆成一个常数多项式。
  4. 上升操作中,前缀和操作可以用多项式表示;而第一个数加上一个数,相当于在这个多项式上加上一个常数;而这些使得这段的 \(f\) 可以看作是序列 1,1,1,... 的多阶前缀和的多项式的和。

于是我们可以大力用多项式维护这个 \(f\) 数组,每次操作直接算出这一段 \(f\) 的前 \(n\) 项,前缀和/偏移一下,然后把新的 \(f\) 直接拉格朗日插值插出来。

有一些细节,比如在下降操作中,第一个位置需要单独新建一个多项式;上升操作中,如果有一段的 \(f\) 已经超出了原来的范围,那么就需要新建一个常数多项式来表示这一段。

**评测链接:AC submission:https://codeforces.ml/problemset/submission/1474/113669336. **

CF1476F. Lanterns

题意:

有 \(n\) 个灯笼拍成一排,第 \(i\) 个灯笼具有 \(p_i\) 的亮度。每个灯笼要么朝向左,照亮左边编号为 \([i - p_i,i - 1]\) 的灯笼,要么朝向右,照亮右边编号为 \([i + 1, i + p_i]\) 的灯笼。

寻找一种方案,为所有的灯笼确定朝向,使得每一个灯笼被至少一个其他灯笼照亮。\(n\le 3\times 10^5\)。

题解:

Try a try,AC is OK!这里是 \(n^2\log n\) 过 \(n=300000\)。

首先有一个 \(O(n^3)\) 的 DP:设 \(f_{i,l,r}\) 表示使用了前 \(i\) 个灯笼、最左边需要点亮的灯笼是 \(l\)、最右边已经照亮了灯笼 \(r\) 的一个状态,然后就可以快乐地 DP 了。

然后我们可以发现:同样的 \(l\),只有最大的 \(r\) 是有用的,于是状态数和复杂度就可以减小到 \(O(n^2)\)。

但是还不能过,我们发现,对于同一个 \(i\),合法的 \(l\) 应该是很少的——因为只有当前的灯笼的威力可以到达 \(l\) 我才会使它朝左,所以我们从左往右只保留这些合法状态来 DP,大致代码如下:

for (int i = 1; i <= n; i++)
{
sort(dp[i - 1].begin(), dp[i - 1].end(), [](node x, node y) { return x.l == y.l ? x.r > y.r : x.l > y.l; });
int mx = -1e9;
for (int j = 0; j < dp[i - 1].size(); j++)
{
node u = dp[i - 1][j];
if (u.r <= mx) continue;
if (u.l >= i - p[i] && u.l != i) dp[i].push_back({ u.r < i ? i : n + 1, u.r, j, 0 });
dp[i].push_back({ min(u.l, u.r < i ? i : n + 1), max(u.r, i + p[i]), j, 1 });
mx = max(mx, u.r);
}
}


这样写,复杂度其实卡不满的,于是就过了。

**评测链接:AC submission:https://codeforces.ml/problemset/submission/1476/113486661. **

CF1430G. Yet Another DAG Problem

题意:

给定一个有向无环图,每条边都有 \(W_i\) 的权重,给每个点分配权值 \(A_i\),对于每条连接 \((u,v)\) 的边,定义其权值为 \(Bi=Au-Av\),要求:

  1. \(Bi>0\)
  2. \(\sum Wi\cdot Bi\) 最小

请输出一种 \(A\) 的分配方案,\(n\le 18\)。

题解:

一开始可能会以为这个状态甚至可以按照拓扑序来贪心,但是最后一个样例就给出了反例——两条长短不同但是端点相同的链,这是不能直接贪心的。

那么我们考虑怎么 DP:一种朴素的状态设置是直接状压下已经填好的每一个点的权值,记为 \(S\);设 \(f_{i,S}\) 表示当前填数填到了 \(i\)、状压为 \(S\) 的最小花费。但是这样复杂度几乎等同于爆搜,行不通。

由于 \(n=18\),所以我们考虑指状压下当前已经填好的数的集合 \(S\),这样复杂度就是 \(2^n\) 的,但是怎么转移呢???

大开脑洞一下,我们费用提前计算,在 \(f_i\to f_j\) 的转移中我们的代价,把其他还没选的状态都算上,也就是代价直接记为 \(i\) 集合所有出边的边权之和。这样做有点反直觉,因为代价竟然和 \(j\) 无关,甚至不用记录当前 DP 到了第几位;但是这样的确是对的。

复杂度最劣是 \(2^{36}\) 的,但是由于状压 DP 常常跑不满,我们相信它能过。

**评测链接:AC submission:https://codeforces.ml/problemset/submission/1430/113711359. **