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}\)

\(\text{My Submission.}\)

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{My Submission}\)

后记

因为被我们强大的绍兴一中搞破防了,所以很水。

\(\text{You slow down time.In your golden hour}\)

posted @ 2024-09-29 16:47  Saltyfish6  阅读(26)  评论(0)    收藏  举报
Document