0%

思路讲解

我们来看看哈希怎么做

参考题解 https://www.luogu.com.cn/article/qpf5y3pt

那么其实最难的就是子串哈希的计算

哈希的递推公式是

hash[i]=s[i]+basehash[i1]hash[i]=s[i]+base*hash[i-1]

由此可得

hash[r]=(s[r]+s[r1]base++s[l1]baserl+1++s[0]baser)hash[r]=(s[r]+s[r-1]*base+\cdots+s[l-1]*base^{r-l+1}+\cdots+s[0]*base^{r})

hash[l1]=(s[l1]+s[l2]base++s[0]basel1)hash[l-1]=(s[l-1]+s[l-2]*base+\cdots+s[0]*base^{l-1})

hash[l:r]=(s[r]+s[r1]base++s[l]baserl)hash[l:r]=(s[r]+s[r-1]*base+\cdots+s[l]*base^{r-l})

可以推得下式(其实可以这么理解,把 hash[l1]hash[l-1] 左移 rl+1r-l+1 位)

hash[l:r]=hash[r]hash[l1]baserl+1hash[l:r]=hash[r]-hash[l-1]*base^{r-l+1}

至于height数组?直接用定义求,不搞那么多花里胡哨的

当然哈希还是慢了一点

image

下面是快排倍增做法

后缀的问题我们已经解决了,height数组是什么?

image

就是排序好的后缀,当前与上一个相同的前缀最长为多少(如上图所示)

那怎么求那?

运用下面这个定理

image

image

AC代码

AC https://www.luogu.com.cn/record/198571599

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
#include <cmath>
#include <bitset>
#include <iterator>
#include <random>
#include <iomanip>
#include <cctype>
#include <array>

using namespace std;

typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> arr;
const ll MAXN=static_cast<ll>(1e6)+10;
const ll /*mod=static_cast<ll>(1e17)+3,*/base=131;
ll N,T,hashe[MAXN],sa[MAXN],height[MAXN],powBase[MAXN];
string S;

inline ll get_hash(ll a,ll b){
// if(a<b)
// return -1;
// 就是b的哈希值-(a-1)的哈希值取模,对a==0的情况特判了
return hashe[b]-(a>0?hashe[a-1]:0)*powBase[b-a+1];
}

inline bool comp(ll a,ll b){
// 使用二分查找来枚举重叠了几位
ll l=0,r=min(N-a,N-b);
while (l<r) {
ll mid=l+r+1>>1;
if(get_hash(a,a+mid-1)==get_hash(b,b+mid-1)){
l=mid;
}else{
r=mid-1;
}
}

if(a+l<N && b+l<N && S[a+l]!=S[b+l]){
return S[a+l]<S[b+l];
}
// 返回长度小的
return N-a<N-b;
}
inline void get_height(){
for(int i=1;i<N;++i){
ll a=sa[i-1],b=sa[i];
// 使用二分查找来枚举重叠了几位
ll l=0,r=min(N-a,N-b);
while (l<r) {
ll mid=l+r+1>>1;
if(get_hash(a, a+mid-1)==get_hash(b, b+mid-1)){
l=mid;
}else{
r=mid-1;
}
}
height[i]=l;
}
}
inline void init(){
ll val=0;
for(int i=0;i<N;++i){
val=base*val+S[i];
hashe[i]=val;
sa[i]=i;
}
powBase[0]=1;
for(int i=1;i<=N+5;++i){
powBase[i]=powBase[i-1]*base;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>S;
N=S.size();
init();
sort(sa, sa+N, comp);
for(int i=0;i<N;++i)
cout<<sa[i]<<' ';
cout<<'\n';
get_height();
for(int i=0;i<N;++i)
cout<<height[i]<<' ';
cout<<'\n';
}
/*
ponoiiipoi
AC https://www.luogu.com.cn/record/198571599
*/

AC 倍增法

https://www.luogu.com.cn/record/198379595

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
#include <cmath>
#include <bitset>
#include <iterator>
#include <random>
#include <iomanip>
#include <cctype>
#include <array>

using namespace std;

typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> arr;
const ll MAXN=static_cast<ll>(3e5)+117;

char S[MAXN];
// rk为第i个后缀子串排第几?
ll rk[MAXN],sa[MAXN],tmp[MAXN],height[MAXN];
ll N;
int K;
bool comp_sa(ll a,ll b){
if(rk[a]!=rk[b]){
return rk[a]<rk[b];
}else{
int ra= a+K<=N ? rk[a+K]:-1;
int rb= b+K<=N ? rk[b+K]:-1;
return ra<rb;
}
}

void cal_sa(){ // 计算后缀数组
// tmp要访问到第N个数
for(int i=0;i<=N;++i){
rk[i]=i<N ? S[i]:-1;
sa[i]=i; // 还没有排序的后缀数组
}
for(K=1;K<=N;K=K*2){
sort(sa, sa+N, comp_sa);
// 排在sa最前面的rk肯定也是最前面的
tmp[sa[0]]=0;
for(int i=0;i<N;++i){
// tmp[sa[0]]排第一个,tmp[sa[0]]为第
// 如果确实小,rk+1(因为这个rk要排logn次,不是一次排出来的)
tmp[sa[i+1]]=tmp[sa[i]]+(comp_sa(sa[i], sa[i+1]) ? 1:0);
}
for(int i=0;i<N;++i){
rk[i]=tmp[i];
}
}
}

void getHeight(){
for(int i=0;i<N;++i){
rk[sa[i]]=i;
}
for(int i=0,k=0;i<N;++i){
if(rk[i]==0) continue;
if(k) k--;
int j=sa[rk[i]-1]; // 前面一个序列的起始位置
while (i+k<N && j+k<N && S[i+k]==S[j+k]) k++;
height[rk[i]]=k;
}
}

int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>S;
N=strlen(S);
cal_sa();
for(int i=0;i<N;++i){
cout<<sa[i]<<' ';
}
cout<<'\n';
getHeight();
for(int i=0;i<N;++i){
cout<<height[i]<<' ';
}
cout<<'\n';
return 0;
}
/*
00000

5 4 3 2 1
AC https://www.luogu.com.cn/record/198379595
*/

心路历程(WA,TLE,MLE……)

WA

https://www.luogu.com.cn/record/198357917

字符串哈希WA了一次,主要原因是整型溢出

https://www.luogu.com.cn/record/198561313

解决方法竟然是不管?用整形溢出自然取整?我不理解,但我大受震撼

思路讲解

后缀数组板题,数据还是有点水,我才用的做法是O(nloglog)的(快排排logn次)

数据不止有点水,和loj比也水,测不出来我代码的问题。

AC代码

https://loj.ac/s/2237091

这个是字符串哈希+二分的做法(洛谷稍微卡了一下,我也就不用快读什么的过了)

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
#include <cmath>
#include <bitset>
#include <iterator>
#include <random>
#include <iomanip>
#include <cctype>
#include <array>

using namespace std;

typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> arr;
const ll MAXN=static_cast<ll>(1e6)+10;
const ll /*mod=static_cast<ll>(1e17)+3,*/base=131;
ll N,T,hashe[MAXN],sa[MAXN],height[MAXN],powBase[MAXN];
string S;

inline ll get_hash(ll a,ll b){
// if(a<b)
// return -1;
// 就是b的哈希值-(a-1)的哈希值取模,对a==0的情况特判了
return hashe[b]-(a>0?hashe[a-1]:0)*powBase[b-a+1];
}

inline bool comp(ll a,ll b){
// 使用二分查找来枚举重叠了几位
ll l=0,r=min(N-a,N-b);
while (l<r) {
ll mid=l+r+1>>1;
if(get_hash(a,a+mid-1)==get_hash(b,b+mid-1)){
l=mid;
}else{
r=mid-1;
}
}

if(a+l<N && b+l<N && S[a+l]!=S[b+l]){
return S[a+l]<S[b+l];
}
// 返回长度小的
return N-a<N-b;
}
inline void get_height(){
for(int i=1;i<N;++i){
ll a=sa[i-1],b=sa[i];
// 使用二分查找来枚举重叠了几位
ll l=0,r=min(N-a,N-b);
while (l<r) {
ll mid=l+r+1>>1;
if(get_hash(a, a+mid-1)==get_hash(b, b+mid-1)){
l=mid;
}else{
r=mid-1;
}
}
height[i]=l;
}
}
inline void init(){
ll val=0;
for(int i=0;i<N;++i){
val=base*val+S[i];
hashe[i]=val;
sa[i]=i;
}
powBase[0]=1;
for(int i=1;i<=N+5;++i){
powBase[i]=powBase[i-1]*base;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>S;
N=S.size();
init();
sort(sa, sa+N, comp);
comp(67,60);
for(int i=0;i<N;++i)
cout<<sa[i]+1<<' ';
cout<<'\n';
}
/*
ponoiiipoi

*/

下面是倍增法

https://loj.ac/s/2236509

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 <cstring>
#include <algorithm>
#include <deque>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
#include <cmath>
#include <bitset>
#include <iterator>
#include <random>
#include <iomanip>
#include <cctype>
#include <array>

using namespace std;

typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<ll, 3> arr;
const ll MAXN = static_cast<ll>(1e6) + 10;

char S[MAXN];
// rk为第i个后缀子串排第几?
ll rk[MAXN], sa[MAXN], tmp[MAXN];
ll N;
int K;
bool comp_sa(ll a, ll b) {
if (rk[a] != rk[b]) {
return rk[a] < rk[b];
} else {
// 其实小于N就是对的,也不用考虑最后一位处理了
int ra = a + K <= N ? rk[a + K] : -1;
int rb = b + K <= N ? rk[b + K] : -1;
return ra < rb;
}
}

void cal_sa() { // 计算后缀数组
// tmp要访问到第N个数
for (int i = 0; i <= N; ++i) {
rk[i] = i < N ? S[i] : -1;
sa[i] = i; // 还没有排序的后缀数组
}

for (K = 1; K <= N; K = K * 2) {
sort(sa, sa + N, comp_sa);
// 排在sa最前面的rk肯定也是最前面的
tmp[sa[0]] = 0;

for (int i = 0; i < N; ++i) {
// tmp[sa[0]]排第一个,tmp[sa[0]]为第
// 如果确实小,rk+1(因为这个rk要排logn次,不是一次排出来的)
tmp[sa[i + 1]] = tmp[sa[i]] + (comp_sa(sa[i], sa[i + 1]) ? 1 : 0);
}

for (int i = 0; i < N; ++i) {
rk[i] = tmp[i];
}
}
}

int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> S;
N = strlen(S);
cal_sa();

for (int i = 0; i < N; ++i) {
cout << sa[i] + 1 << ' ';
}

cout << '\n';
return 0;
}

心路历程(WA,TLE,MLE……)

AC(其实这个代码里有UB,我越界访问了,不过问题不大)

https://www.luogu.com.cn/record/198321780

虽然洛谷的AC了,但是loj的出了问题

hack数据

00000

https://loj.ac/s/2236497

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
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
#include <cmath>
#include <bitset>
#include <iterator>
#include <random>
#include <iomanip>
#include <cctype>
#include <array>

using namespace std;

typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> arr;
const ll MAXN=static_cast<ll>(1e6)+10;

string S;
// rk为第i个后缀子串排第几?
ll rk[MAXN],sa[MAXN],tmp[MAXN];
ll N;
int K;
bool comp_sa(ll a,ll b){
if(rk[a]!=rk[b]){
return rk[a]<rk[b];
}else{
int ra= a+K<=N ? rk[a+K]:-1;
int rb= b+K<=N ? rk[b+K]:-1;
return ra<rb;
}
}

void cal_sa(){ // 计算后缀数组
// tmp要访问到第N个数
for(int i=0;i<=N;++i){
rk[i]=S[i];
sa[i]=i; // 还没有排序的后缀数组
}
for(K=1;K<=N;K=K*2){
sort(sa, sa+N, comp_sa);
// 排在sa最前面的rk肯定也是最前面的
tmp[sa[0]]=0;
for(int i=0;i<N;++i){
// tmp[sa[0]]排第一个,tmp[sa[0]]为第
// 如果确实小,rk+1(因为这个rk要排logn次,不是一次排出来的)
tmp[sa[i+1]]=tmp[sa[i]]+(comp_sa(sa[i], sa[i+1]) ? 1:0);
}
for(int i=0;i<N;++i){
rk[i]=tmp[i];
}
}
}

int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>S;
N=S.size();
cal_sa();
for(int i=0;i<N;++i){
cout<<sa[i]+1<<' ';
}
cout<<'\n';
return 0;
}

思路讲解

假设已经知道height数组以及后缀数组,这样子求出来的就是最长公共子串

1
2
3
4
5
6
7
8
9
10
S=s1+'$'+s2;
for(int i=1;i<N;++i){
ll l=sa[i-1],r=sa[i];
if(l>r)
swap(l, r);
if(l<n1 && r>n1){
ans=max(ans,height[i]);
}
}
cout<<ans<<'\n';

AC代码

AC 倍增法还是快

https://vjudge.net/solution/57548921

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <deque>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
#include <cmath>
#include <bitset>
#include <iterator>
#include <random>
#include <iomanip>
#include <cctype>
#include <array>

using namespace std;

typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> arr;
const ll MAXN=static_cast<ll>(2e5)+10;
const ll base=131;

ll N,tmp[MAXN],sa[MAXN],height[MAXN],rk[MAXN],K;
string S;

bool comp_sa(ll a,ll b){
if(rk[a]!=rk[b]){
return rk[a]<rk[b];
}else{
int ra= a+K<=N ? rk[a+K]:-1;
int rb= b+K<=N ? rk[b+K]:-1;
return ra<rb;
}
}

inline void cal_sa(){ // 计算后缀数组
// tmp要访问到第N个数
for(int i=0;i<=N;++i){
rk[i]=i<N ? S[i]:-1;
sa[i]=i; // 还没有排序的后缀数组
}
for(K=1;K<=N;K=K*2){
sort(sa, sa+N, comp_sa);
// 排在sa最前面的rk肯定也是最前面的
tmp[sa[0]]=0;
for(int i=0;i<N;++i){
// tmp[sa[0]]排第一个,tmp[sa[0]]为第
// 如果确实小,rk+1(因为这个rk要排logn次,不是一次排出来的)
tmp[sa[i+1]]=tmp[sa[i]]+(comp_sa(sa[i], sa[i+1]) ? 1:0);
}
for(int i=0;i<N;++i){
rk[i]=tmp[i];
}
}
}

inline void getHeight(){
for(int i=0;i<N;++i){
rk[sa[i]]=i;
}
for(int i=0,k=0;i<N;++i){
if(rk[i]==0) continue;
if(k) k--;
int j=sa[rk[i]-1]; // 前面一个序列的起始位置
while (i+k<N && j+k<N && S[i+k]==S[j+k]) k++;
height[rk[i]]=k;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
string s1,s2;
while(cin>>s1>>s2){
S=s1+'$'+s2;
N=S.size();
cal_sa();
getHeight();
// < n1 是前面的串 >n1 是后面的串
ll n1=s1.size();
ll ans=0;
for(int i=1;i<N;++i){
ll l=sa[i-1],r=sa[i];
if(l>r)
swap(l, r);
if(l<n1 && r>n1){
ans=max(ans,height[i]);
}
}
cout<<ans<<'\n';
}
return 0;
}
/*
AC https://vjudge.net/solution/57548921
banana
cianaic
abbacc
abbaccc

EOF

*/

心路历程(WA,TLE,MLE……)

https://vjudge.net/solution/57547694

TLE,我用快读试试能不能搞定用哈希二分过

思路讲解

我们看看可不可以一题多解,用数位dp解一下这道

主要就是记忆化搜索,具体看黑书还有我的代码注释,还是比较详细的

黑书还是好,让我仿写,但还可以留白一部分,让我自己写,真是对我太友好了

image

AC代码

https://vjudge.net/solution/57465304

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
80
81
82
83
84
85
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
#include <cmath>
#include <bitset>
#include <iterator>
#include <random>
#include <iomanip>
#include <cctype>
#include <array>

using namespace std;

typedef long long ll;
typedef pair<ll,ll> pll;
typedef array<ll,3> arr;
const ll MAXN=static_cast<ll>(2e5)+10;
// dp表示第i位符合要求的数字数量,比如dp[1]=9,因为除了4都满足
ll L,R,dp[15][3],digit[15];
// digit是指该数的第i位为几,比如说342,digit[1]=2
// 上一位是不是6?
ll dfs(ll len,bool isMax,bool is6){
if(len==0)// 递归结束条件
return 1;
// 记忆化结束条件
if(dp[len][is6]!=-1 && isMax==false)
return dp[len][is6];
ll res=0,maxx;
// 如果不是最大位,那么就直接取9
maxx= isMax ? digit[len]:9;
for(int i=0;i<=maxx;++i){
// 记忆化搜索关键部分来了,数位dp之所以可以这样
// 是因为 最高位+后面的数=新数
if(i==4) // 排除4
continue;
if(is6 && i==2) // 不能组成62
continue;
if(i==6){
res+=dfs(len-1,isMax && i==maxx,true);
}else{
// 如果前数进入是最大数,i也是最大数,后面的也要受限制
// 比如324,百位为3确定了,十位最多为2
// 但如果324,百位为2确定了,后面的数想写啥写啥
res+=dfs(len-1,isMax && i==maxx,false);
}
}
if(!isMax)
dp[len][is6]=res;
return res;
}

void solve(){
memset(dp, -1, sizeof(dp));
string Ls=to_string(L-1);
for(int i=1;i<=Ls.size();++i){
digit[i]=Ls[Ls.size()-i]-'0';
}
ll lres=dfs(Ls.size(), true,false);
string Rs=to_string(R);
for(int i=1;i<=Rs.size();++i){
digit[i]=Rs[Rs.size()-i]-'0';
}
ll rres=dfs(Rs.size(), true,false);
cout<<rres-lres<<'\n';
return;
}

int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
while (cin>>L>>R) {
if(L==0 && R==0)
break;
solve();
}
return 0;
}
// AC https://vjudge.net/solution/57465304

心路历程(WA,TLE,MLE……)

思路讲解

注意石子是一个圈,这个时候化曲为直,化为长度为两倍的序列

然后区间dp一般是O(n**3)的,因为序列的合并可能发生在任何地方,不一定是头和尾

AC代码

AC https://www.luogu.com.cn/record/197959001

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
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
#include <cmath>
#include <bitset>
#include <iterator>
#include <random>
#include <iomanip>
#include <cctype>
#include <array>

typedef long long ll;
typedef std::pair<ll,ll> pll;
typedef std::array<ll,3> arr;
const ll MAXN=217;

ll N,T,A[MAXN],dp_min[MAXN][MAXN],dp_max[MAXN][MAXN],sumA[MAXN];

int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);std::cout.tie(0);
std::cin>>N;
for(int i=1;i<=N;++i){
std::cin>>A[i];
sumA[i]=A[i]+sumA[i-1];
}
for(int i=N+1;i<=2*N;++i){
A[i]=A[i-N];
sumA[i]=A[i]+sumA[i-1];
}

// 初始化dp数组
for(int i=1;i<=2*N;++i){
dp_min[i][i] = 0;
dp_max[i][i] = 0;
}

// 计算最小得分
for(int len=2;len<=N;++len){
for(int i=1;i<=2*N-len+1;++i){
int j = i + len - 1;
dp_min[i][j] = 1e18+7;
dp_max[i][j] = 0;
for(int k=i;k<j;++k){
ll cost = sumA[j] - sumA[i-1];
dp_min[i][j] = std::min(dp_min[i][j], dp_min[i][k] + dp_min[k+1][j] + cost);
dp_max[i][j] = std::max(dp_max[i][j], dp_max[i][k] + dp_max[k+1][j] + cost);
}
}
}

ll min_ans = 1e18+7;
ll max_ans = 0;
for(int i=1;i<=N;++i){
min_ans = std::min(min_ans, dp_min[i][i+N-1]);
max_ans = std::max(max_ans, dp_max[i][i+N-1]);
}
std::cout<<min_ans<<"\n";
std::cout<<max_ans<<"\n";
return 0;
}
/*
AC https://www.luogu.com.cn/record/197959001
*/

心路历程(WA,TLE,MLE……)

WA

https://www.luogu.com.cn/record/197954954

序列的合并可能发生在任何地方,不一定是头和尾

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
80
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <unordered_map>
#include <cmath>
#include <bitset>
#include <iterator>
#include <random>
#include <iomanip>
#include <cctype>
#include <array>

typedef long long ll;
typedef std::pair<ll,ll> pll;
typedef std::array<ll,3> arr;
const ll MAXN=217;

ll N,T,A[MAXN],dp[MAXN][MAXN],sumA[MAXN];


int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);std::cout.tie(0);
std::cin>>N;
for(int i=1;i<=N;++i){
std::cin>>A[i];
sumA[i]=A[i]+sumA[i-1];
}
for(int i=N+1;i<=2*N;++i){
A[i]=A[i-N];
sumA[i]=A[i]+sumA[i-1];
}
// 注意石子是一个圈,所以我们搞一个长度为2*n的链
// 先最小化得分
for(int i=2*N;i>=1;--i){
for(int j=i+1;j<=2*N;++j){
// 从i到j 区间 最小得分 从i+1到j区间转移过来
dp[i][j]=std::min(dp[i+1][j]+sumA[j]-sumA[i]+A[i],
// 从i到j-1区间转移过来
dp[i][j-1]+sumA[j-1]-sumA[i-1]+A[j]);

}
}
ll ans=1e18+7;
for(int i=1;i<=N;++i){
ans=std::min(dp[i][i+N-1],ans);
}
std::cout<<ans<<"\n";
std::memset(dp, 0, sizeof(dp));
// 先最大化得分
for(int i=2*N;i>=1;--i){
for(int j=i+1;j<=2*N;++j){
// 从i到j 区间 最大得分 从i+1到j区间转移过来
dp[i][j]=std::max(dp[i+1][j]+sumA[j]-sumA[i]+A[i],
// 从i到j-1区间转移过来
dp[i][j-1]+sumA[j-1]-sumA[i-1]+A[j]);
}
}
ans=0;
for(int i=1;i<=N;++i){
ans=std::max(dp[i][i+N-1],ans);
}
std::cout<<ans<<"\n";
return 0;
}
/*
4
4 5 9 4

4616 4 14 12 0 3 11 8 18 2 6 8 6 7 13 7 8 14 11 2 16 12 16 0 8 1 3 10 7 16 0 16 11 17 13 18 5 15 0 12 19 0 0 5 3 1

2087
10554
*/