0%

JOI 2025 Final 只不过是长的领带 2 / Just Long Neckties 2 题解

题目链接:洛谷 P11665

题目即从原序列取出若干个数形成新序列,且原序列中相邻两个数至少取一个,求新序列最少用几个不下降子序列可以覆盖。

考虑贪心,如果原序列的某个数要放进新序列,就把它安排在当前所有不下降子序列中,结尾的数小于等于它的最大的一个后面,找不到就新加一个序列。可以发现,过程中某一时刻,所有不下降子序列的最后一个数都不同,可以用 $2^{21}$ 种状态表示。

于是有一个简单的 DP,记 $f_{i,S}$ 表示当前考虑到第 $i$ 个数,不下降子序列的状态为 $S$ 是否可行。

如果 $f_{i,S}=1$,且 $a_{i+1} \in S$,则 $f_{i+1,S}=1$;同理,如果 $f_{i,S}=1$ 且 $a_{i+2} \in S$,则 $f_{i+2,S}=1$。也就是说,从一个 $f_{i,S}=1$ 的状态,可以一直推到 $f_{j-1,S}=1$,其中 $j$ 是最小的满足 $a_j \notin S$,$a_{j+1} \notin S$ 的数。这个 $j$ 可以通过预处理后暴力得出(对每个位置,预处理 $nxt_{i,x}$ 为大于 $i$ 的最小的 $j$,使得 $a_j=x$;$nxtt_{i,x}$ 为大于等于 $i$ 的最小的 $j$,使得 $a_{j+1}=x$;查询时暴力往后跳)。发现对于一个状态 $S$,重要的只有最大的 $k$ 使得 $f_{k,S}=1$,记这样的 $k$ 为 $g_S$。此时的 $j$ 即为 $g_S + 1$。

然后就可以得出 $g_S$ 的求解方法。考虑从小到大计算,由于 $a_{g_S + 1}$ 和 $a_{g_S + 2}$ 至少要选一个,下一个放进不下降子序列集合的数一定是这两个之一。分类讨论放哪一个数,得出新的 $S’$,再计算 $g_{S’}$ 即可。若此时发现 $S’$ 可以把剩下的序列全部覆盖掉,则更新答案。

总的时间复杂度为 $\mathcal{O}(nV+V^22^V)$,此处 $V=\max{a_i}=21$。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
#include <cstdio>
using namespace std;

int n;
int a[5000010];
int nxt[5000010][23],nxtt[5000010][23],las[23];
int f[(1 << 21) + 10],ans = 40;
int cnt[(1 << 21) + 10];

int main()
{
scanf("%d",&n);
for (int i = 1;i <= n;i ++) scanf("%d",a + i);
for (int i = n;i >= 1;i --)
{
for (int j = 1;j <= 21;j ++) nxtt[i][j] = nxtt[las[a[i]]][j];
nxtt[i][a[i + 1]] = i + 1;
for (int j = 1;j <= 21;j ++) nxt[i][j] = las[j];
las[a[i]] = i;
}
for (int i = 1;i <= 21;i ++) nxt[0][i] = las[i];
for (int i = 0;i < (1 << 21);i ++) cnt[i] = cnt[i >> 1] + (i & 1);
for (int i = 0;i < (1 << 21);i ++)
{
if (!f[i] && i) continue;
int Nxt = f[i] + 1;
int res = 0,tnxt = Nxt;
Nxt = n + 10;
for (int j = a[tnxt] - 1;j >= 0;j --)
if ((i >> j & 1) || !j)
{
int tmp = (i & ((1 << 21) - 1 - (1 << j))) ^ (1 << (a[tnxt] - 1));
res = tmp;
for (int k = 0;k < 21;k ++)
{
if (tmp >> k & 1) continue;
int t = nxt[tnxt][k + 1];
if (!t) continue;
for (int w = 0;w < 21;w ++)
{
if (tmp >> w & 1) continue;
int tt = nxtt[t][w + 1];
if (!tt) continue;
Nxt = min(Nxt,tt - 1);
}
}
break;
}
if (Nxt >= n)
ans = min(ans,cnt[res]);
else f[res] = max(f[res],Nxt - 1);
Nxt = n + 10;
for (int j = a[tnxt + 1] - 1;j >= 0;j --)
if ((i >> j & 1) || !j)
{
int tmp = (i & ((1 << 21) - 1 - (1 << j))) ^ (1 << (a[tnxt + 1] - 1));
res = tmp;
for (int k = 0;k < 21;k ++)
{
if (tmp >> k & 1) continue;
int t = nxt[tnxt + 1][k + 1];
if (!t) continue;
for (int w = 0;w < 21;w ++)
{
if (tmp >> w & 1) continue;
int tt = nxtt[t][w + 1];
if (!tt) continue;
Nxt = min(Nxt,tt - 1);
}
}
break;
}
if (Nxt >= n)
ans = min(ans,cnt[res]);
else f[res] = max(f[res],Nxt - 1);
}
printf("%d\n",ans);
}