目录

[luogu T71973]卡常者π酱

迁移提示
本文迁移自博客园, 原文链接

[luogu T71973]卡常者π酱

题意

给定一个长度为 $n$ 的字符串, 要求将字符串分割为若干段, 每一段要么是一个字符要么是前面几段的并的子串.

如果某一段是一个单独字符, 则产生 $a$ 的开销.

如果是前几段的并的子串, 则产生 $b$ 的开销.

如果满足两个条件, 则可以在 $a,b$ 中任选一个开销.

求划分的最小开销.

$n\le 5\times 10^6$, 字符集大小 $\Sigma\le 7$.

题解

冷静分析一下发现是沙雕题

然而题目说不卡常实际上是真的卡常…严格 $O(n)$ 并不一定能跑过…

我们发现这个沙雕题最后一个串的开销和前面的划分方案无关, 果断想到DP. 设 $dp_i$ 表示长度为 $i$ 的前缀的最小划分代价.

然后有个显然的性质, 就是如果有一个后缀满足它在前面出现过, 那么这个后缀的所有后缀同样都满足. 所以不难想到对于当前DP前缀找到满足该性质的最长的后缀, 然后在这个后缀的所有后缀中找最小DP值更新.

不难发现这个过程实际上可以用后缀自动机解决. 对于每个状态都维护一下 $right$ 集合中的最小值, 也就是当前状态所代表字符串的第一次出现的右端点的位置. 这样我们就可以直接知道当前后缀是否在前面出现过了.

不难发现这个后缀的左端点位置也是单调的, 所以不合法暴力跳就可以 $O(n)$ 了.

按照刚刚的讨论我们需要把所有合法的后缀的 $dp$ 值取 $\min$, 需要单调队列来维护. 但是实际上不难发现 $dp$ 值是单调不降的, 所以直接取最长合法后缀的左端点处的 $dp$ 值就可以了.

但是要想A这题还得加点优化. 一个是 $right$ 集合的最小值的维护, 并不需要给后缀自动机结点基数排序. 因为每次插入的新点的 $right$ 集合中的值实际上是单调递增的, 而且如果在原来自动机上某个点在另一个点的子树中的话扩展后一定还在那个点的子树里. 所以可以边构造边算.

其次是我们并不需要先构造出整个SAM然后再跑, 我们完全可以边构造边计算最长满足条件的后缀. 因为后面的字符串并不会影响前面已有的信息.

加了这两个优化才卡时限过的…这可真蠢.

(不难分析得到不加两个优化+单调队列实际上也是严格 $O(n)$ 的时间复杂度)

参考代码

 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 <bits/stdc++.h>

const int MAXN=1e7+10;
typedef long long intEx;

int n;
int a;
int b;
int cnt=1;
int last=1;
int root=1;
int s[MAXN];
int buc[MAXN];
int len[MAXN];
int prt[MAXN];
int minr[MAXN];
char str[MAXN];
intEx dp[MAXN];
int chd[MAXN][7];

void Extend(char);

int main(){
	scanf("%d%d%d",&n,&a,&b);
	scanf("%s",str+1);
	int cur=root,curlen=0;
	for(int i=1;i<=n;i++){
		Extend(str[i]);
		int p=str[i]-'a';
		while(!chd[cur][p]){
			cur=prt[cur];
			curlen=len[cur];
		}
		++curlen;
		cur=chd[cur][p];
		while(cur!=root&&minr[cur]>i-curlen){
			if(i-minr[cur]>len[prt[cur]])
				curlen=i-minr[cur];
			else{
				cur=prt[cur];
				curlen=len[cur];
			}
		}
		dp[i]=dp[i-1]+a;
		if(curlen)
			dp[i]=std::min(dp[i],dp[i-curlen]+b);
	}
	printf("%lld\n",dp[n]);
	return 0;
}

void Extend(char ch){
	int x=ch-'a';
	int p=last;
	int np=++cnt;
	last=np;
	minr[np]=len[np]=len[p]+1;
	while(p&&!chd[p][x])
		chd[p][x]=np,p=prt[p];
	if(!p)
		prt[np]=root;
	else{
		int q=chd[p][x];
		if(len[q]==len[p]+1)
			prt[np]=q;
		else{
			int nq=++cnt;
			memcpy(chd[nq],chd[q],sizeof(chd[q]));
			len[nq]=len[p]+1;
			prt[nq]=prt[q];
			prt[q]=nq;
			prt[np]=nq;
			minr[nq]=minr[q];
			while(p&&chd[p][x]==q)
				chd[p][x]=nq,p=prt[p];
		}
	}
}

https://pic.rvalue.moe/2021/08/02/1d11574a5566c.png