CF368B Sereja and Suffixes 题解

Content

\(n\) 个数 \(a_1,a_2,a_3,...,a_n\)。有 \(m\) 次询问,每次给定一个数 \(l\),查询在区间 \([l,n]\) 内不同的数一共有多少个。

数据范围:\(1\leqslant n,m\leqslant 10^5,1\leqslant a_i\leqslant 10^5\)

Solution

鉴于 \(\mathcal{O}(nm)\) 的暴力模拟显然不能够通过此题,我们需要一种更高效的算法,以卡过这样的数据。

那么,我们自然而然就会想到 \(\texttt{dp}\) 的算法了。

既然是要求 \([l,n]\) 里面的不同的数的个数,那么我们不妨就设 \(dp_i\)\([i,n]\) 里面不同的数的个数。那么,我们显然可以先推出这样一个式子:

\[dp_n=1 \]

很显然,\([n,n]\) 里面不同的数只有 \(a_n\) 一个,对吧?那么我们就从这个临界点开始从右往左进行 \(\texttt{dp}\)

为了统计这个数字是否出现,我们需要开一个 \(vis\) 数组(这里 \(a_i\) 的值不算太大,如果很大的时候就需要用到 \(\texttt{map}\) 来实现了)。那么,这个题目的转化方程就很容易想到了:如果在 \(i\) 之后出现了一个数 \(j\),使得 \(a_j=a_i\),那么 \(dp_i\) 就保持和 \(dp_{i+1}\) 不变,否则,又出现了一个不同的数,那么 \(dp_i\) 相比较于 \(dp_{i+1}\) 就会多 \(1\)

这样,转移方程就列完了:

\[dp_i=\begin{cases}1&i=n\\dp_{i+1}+1&vis_{a_i}=0,1\leqslant i<n\\dp_{i+1}&vis_{a_i}=1,1\leqslant i<n\end{cases} \]

我们拿样例 \(1\) 做个例子:

1 2 3 4 1 2 3 4 100000 99999

显然,\(dp_{10}=1\)

更新 \(vis_{99999}=1\),从 \(10\) 这个位置开始从右往左扫。

  1. 扫到 \(9\) 这个位置,显然,后面并没有出现 \(100000\) 这个数,所以 \(dp_9=dp_{10}+1=2\),并更新 \(vis_{100000}=1\)

  2. 扫到 \(8\) 这个位置,显然,后面并没有出现 \(4\) 这个数,所以 \(dp_8=dp_9+1=3\),并更新 \(vis_4=1\)

  3. 扫到 \(7\) 这个位置,显然,后面并没有出现 \(3\) 这个数,所以 \(dp_7=dp_8+1=4\),并更新 \(vis_3=1\)

  4. 扫到 \(6\) 这个位置,显然,后面并没有出现 \(2\) 这个数,所以 \(dp_6=dp_7+1=5\),并更新 \(vis_2=1\)

  5. 扫到 \(5\) 这个位置,显然,后面并没有出现 \(1\) 这个数,所以 \(dp_5=dp_6+1=6\),并更新 \(vis_1=1\)

  6. 扫到 \(4\) 这个位置,显然,位置 \(8\) 出现了 \(4\) 这个数,有 \(vis_4=1\),所以 \(dp_4=dp_5=6\)。无须再更新 \(vis_4\) 了,下同。

  7. 扫到 \(3\) 这个位置,显然,位置 \(7\) 出现了 \(3\) 这个数,有 \(vis_3=1\),所以 \(dp_3=dp_4=6\)

  8. 扫到 \(2\) 这个位置,显然,位置 \(6\) 出现了 \(2\) 这个数,有 \(vis_2=1\),所以 \(dp_2=dp_3=6\)

  9. 扫到 \(1\) 这个位置,显然,位置 \(5\) 出现了 \(1\) 这个数,有 \(vis_1=1\),所以 \(dp_1=dp_2=6\)

这样,所有的 \(dp\) 数组全部更新完了:

\[\begin{cases}dp_1=6\\dp_2=6\\dp_3=6\\dp_4=6\\dp_5=6\\dp_6=5\\dp_7=4\\dp_8=3\\dp_9=2\\dp_{10}=1\end{cases} \]

显然,这和样例所给出的答案完全相符。相信对这个 \(\texttt{dp}\) 的过程有了一定的加深理解。

那么,既然\(\mathcal{O}(n)\)\(\texttt{dp}\) 预处理完了,剩下的询问就可以直接 \(\mathcal{O}(1)\) 轻松搞定了,这下就可以轻松通过此题了。

Code

本题只放 \(\texttt{dp}\) 过程的核心代码部分,其中:

  • \(dp_i\) 表示 \([i,n]\) 内不同的数的个数。
  • \(vis_i\) 表示 \(i\) 是否出现过,是的话 \(vis_i=1\),否则 \(vis_i=0\)
f[n] = 1;
vis[a[n]] = 1;
for(int i = n - 1; i >= 1; --i) {
	if(!vis[a[i]]) {
		f[i] = f[i + 1] + 1;
		vis[a[i]] = 1;
	} else	f[i] = f[i + 1];
}
posted @ 2021-12-23 20:15  Eason_AC  阅读(34)  评论(0)    收藏  举报