NOIP2024模拟赛9 赛后总结
前言
听说把枕头哭湿,晚上可以梦见大海
先说明一下情况。
我 \(\text{T2}\),同样的数据,本地 \(\text{500ms}\to\) \(\text{sxyz: }1.7\texttt{s}\)。
\(\text{T3}, \text{CF 3s}\) 的时限,什么烂机子开 \(\text{1s}\)。
我们都有光明的未来。
我尽量克制住自己的情绪。
B / ABC176F
很智慧的一道题,稍微总结一下。
首先你显然有 \(dp_{i,j,k}\) 表示经过 \(i\) 次操作,前面剩下一个 \(j\) 一个 \(k\) 可以得到的最大分数。
暴力转移其实是比较简单的,假设这一次操作除了 \(j,k\) 另外三个数是 \(a,b,c\)。
-
把 \(a,b,c\) 删掉,\(dp_{i,j,k}=dp_{i-1,j,k}+[a=b=c]\)。
-
把 \(a,b,c\) 中的两个和 \(j,k\) 中的一个删掉,不妨设我们删的是 \(a,b,j\),\(dp_{i,c,k}=dp_{i-1,j,k}+[a=b=j]\)。
-
把 \(a,b,c\) 中的一个和 \(j,k\) 删掉,不妨设我们删的是 \(a,j,k\),\(dp_{i,b,c}=dp_{i-1,j,k}+[a=j=k]\)。
观察到第一种转移可以看作是全局加 \([a=b=c]\) 可以直接写一个全局增量标记在前面,最后答案直接 \(\text{ans}+\text{add}\) 即可。
至于后面两个,观察到变化的状态只有 \(\mathcal{O}(n)\) 个。并且变化一定是变大(因为有第一种转移的存在)
故你考虑把所有变化状态暴力取出来,然后维护一些最大值数组即可。
由于你每次只改变了那些会改变的,故总复杂度 \(\mathcal{O}(n^2)\)
很难崩的是,这个题你一旦常数大了一点你就会喜提 \(\text{TLE 95}\)。
C / CF407D
\(\texttt{2700}\) 的题,但是感觉又不完全算的上。
题解非常清晰明了,这里懒得讲了,讲一下自己的巨恶心做法。
为了将这个做法优化到 \(\mathcal{O}(n^3)\) 我也是煞费苦心了。
说实话这个做法真的挺难有语言描述的。
首先你考虑维护一个 \(\text{num}_{i,l,r}\),表示对于第 \(i\) 行,找到最小的 \(j\ge i\),存在一个数 \(k_1,k_2\in[l,r]\),满足 \(a_{i,k_1}=a_{j,k_2}\)。
接着维护一个 \(\text{nxt}_{i,j}\),表示找到一个最大的 \(k\) 使得 \(a_{i,[j,k]}\) 中的数互不相同。
有了 \(\text{nxt}_{i,j}\),我们考虑再维护一个 \(b_{i,j,k}\),表示找到最大的 \(l\ge i\),使得对于 \(\forall x\in[i,l],\text{nxt}_{x,j}\ge k\),如果没有满足的,就是 \(0\)。
有了这三个数组之后,答案应该是非常好维护的。
我们考虑枚举这个长方形的的最上面一行,假设是第 \(i\) 行的 \([l,r]\) 这个区间。
答案应该是 \(\min(\min_{j=i}^{\text{num}_{i,l,r}}\text{num}_{j,l,r}-i+1,b_{i,l,r-l+1})\times (r-l+1)\)。
感觉还是比较好理解的。至于里面的那一坨 \(\min\),它有一些性质,你可以考虑打一个后缀最小值,具体细节可以看代码。
里面唯一的难点在于 \(\text{num}\) 的求法。
观察到 \(\text{num}_{i,l,r}=\min(\text{num}_{i,l+1,r},\text{num}_{i,l,r-1})\),除了这两个产生的贡献,剩下的就只有第 \(l\) 和第 \(r\) 列对 \(a_{i,l},a_{i,r}\) 产生的贡献了。
如下:
for (int len = 1; len <= m; ++len)
{
for (int l = 1, r = len; r <= m; ++l, ++r)
{
for (int k = n; k >= 1; --k)
{
if(r != l) num[k][l][r] = min(num[k][l + 1][r], num[k][l][r - 1]);
num[k][l][r] = min(num[k][l][r], min(bucket[a[k][l]], bucket[a[k][r]]));
bucket[a[k][l]] = min(bucket[a[k][l]], (short)k);
bucket[a[k][r]] = min(bucket[a[k][r]], (short)k);
}
for (int k = 1; k <= n; ++k) bucket[a[k][l]] = bucket[a[k][r]] = 0x3f3f;
// for (int i = 1; i <= n; ++i) cout << i << " " << l << " " << r << " " << num[i][l][r] << endl;
}
}
其实还有一堆细节,可以考虑直接看代码。
人傻常数大啊啊啊啊,每次都是这种恶心的做法,还要被卡常,难崩。
D / ARC118E
感觉考试的时候思考方向完全不对,以及第一篇这个形式化题解其实讲的挺清楚的。
我们定义 \(S\) 为我们走的路径所对应的点集,\(T\) 为所有的障碍的点集,\(N\) 为当前已经确定的状态的点集,
题目即求 \(\sum_S \sum_{N\subset T} [S \cap T=0]\)。
在此基础上我们考虑一个容斥。
答案就会变成 \(\sum_S \sum_T \sum_{S'\subset S\cap T} (-1)^{|S'|}\)。
观察到这个一点也不好求,于是我们考虑换一下位置:
\(\sum_S \sum_{S'\subset S}(-1)^{|S'|}\times (\sum_{S' \subset T}1)\)
还可以把最后一个循环去掉:
\(\sum_S \sum_{S'\subset S}(-1)^{|S'|}\times (|n-S' \cup N|)!\)。
这个时候应该比较好 \(\texttt{DP}\) 了,因为我们本质上只需要知道 \(|S'\cup N|\),外面的阶乘就有了,然后记录一下方案数就行了。
故我们定义 \(dp_{i,j,k,0/1,0/1}\),表示当前走到了 \((i,j)\),\(|S'\cup N|=k\),\(0/1,0/1\) 分别表示第 \(i\) 行,第 \(j\) 列是否已经有障碍了的方案数。(这两个 \(0/1\) 主要是来看你当前能不能搞出一个障碍。)
转移可以直接见代码,最后直接乘上容斥系数即可,重点要了解的是这个容斥是怎么来的。
后记
因为被我们强大的绍兴一中搞破防了,所以很水。
\(\text{You slow down time.In your golden hour}\)

浙公网安备 33010602011771号