Codeforces Global Round 7

题目

http://codeforces.com/contest/1326/problem/D2

字符串s 去掉其中连续一段,使得剩余的部分构成回文,求最长的方案

范围

|s| <= 10^6

题解

这个题有两个版本,easy的版本|s|<=5000

对于那个版本,先建立r[i][j]表示s[i..j]是否是回文 空间和时间都是O(n^2)

然后枚举 首尾的 长度,和中间循环节的长度,时间复杂度O(n^2)

对于hard版本(当前版本)这样做显然会超时。


竟然是贪心。。。

果然单调性质

假设前后缀最长的对称部分为k,则有两种情况

[0...k][回文].....[k...0]

[0...k].....[回文][k...0]

如果有一个更优的方案(t<k),则它的样式为

[0..t][回文]....[t..0]

[0..t]....[回文][t..0]

因为对称关系,我们只讨论一种剩余回文在左侧的

[0..t][回文]...[t..0]

因为t<k

所以我们可以把回文部分左右减少1个,而把[0..t]变为[0..t+1],它依然是合法的

[0..t+1][回文去首尾]..[t+1..0]

这样新的合法的依然是长度不变

反复操作,最后能得到一个[0...k][回文].....[k...0]

所以任何更优方案能转换成[0...k][回文].....[k...0]

综上,实现变为 移除首位最长对称串,再在剩余的内容中找最长前缀回文或最长后缀回文

最后在中间的找前后缀回文用点kmp之类或者hash之类的就行了

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define INF 0x3f3f3f3f3f3f3f3f
#define MOD 1000000007
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define foreach(i, v) for (__typeof((v).begin()) i = (v).begin(); i != (v).end(); ++ i)
const double pi = acos(-1.0);

// t = a+b
// t = s[pre] + s[suffix]
// len(t) <= len(s)
// ==>
//
// [preA][...]...[~preA]
// [preA]...[...][~preA]
//
//
// = s 移除连续一段 使得结果变成回文
// s[....]
// ~s[....]

// dp[i][j] [0/1/2][0/1/2]
// n^2 ?
//
// s[...i[ ]i...]
// = max(2*i+ [i+1...]回文 ])
// = max(2*i+ [ ...[...?]回文] )


char s[1'000'010];

char t1[1'000'010];
char t2[1'000'010];
int pre[1'000'010];

int r(char * ch,int sz){
pre[0] = -1;
rep(i,1,sz){
int pos = i-1;
while(true){
pos = pre[pos];
if(ch[pos+1] == ch[i]){
pre[i] = pos+1;
break;
}
if(pos == -1){
pre[i] = -1;
break;
}
}
}
// rep(i,0,sz){
// printf("%d ",pre[i]);
// }

int lastans = -1;
per(i,0,sz){
// [[0..lastans]..i[..0]...]
while(true){
if(ch[i] == ch[lastans+1]){
if(lastans + 2 >= i){
return lastans*2+3+ (i!=lastans+1);
}
lastans++;
break;
}
if(lastans == -1){
break;
}
lastans = pre[lastans];
}
}
return 0;
}

int main(){
int t;
cin>>t;
while(t--){
scanf("%s",s);
int n = strlen(s);
int k = -1;
rep(i,0,n){
if(i<n-1-i){
if(s[i]==s[n-i-1]){
k = i;
}else{
break;
}
}
}

rep(i,k+1,n-k-1){
t1[i-k-1]=s[i];
}
int len1 = r(t1,n-2-2*k);
rep(i,k+1,n-k-1){
t2[n-k-2-i]=s[i];
}
int len2 = r(t2,n-2-2*k);
// cout<<"?"<<len1<<endl;
// cout<<"!"<<len2<<endl;
rep(i,0,k+1){
printf("%c",s[i]);
}
if(len1 > len2){
rep(i,0,len1){
printf("%c",t1[i]);
}
}else{
rep(i,0,len2){
printf("%c",t2[i]);
}
}
rep(i,n-k-1,n){
printf("%c",s[i]);
}
printf("\n");
}
return 0;
}

参考

官方题解 http://codeforces.com/blog/entry/74961

总结

估计还是估计到了 O(n)或者O(nlog n)范围的算法,但赛内没有推出更优的性质