Luogu 1117 [NOI2016 优秀的拆分]

传送门

我大概率是一个错解

题目大意

给定一个长度为$n\le30000$的字符串$S$。

如果AB都是非空字符串,那么AABB就是一个优秀的拆分。AB可以相同。

求$S$的所有子串中有多少个优秀的拆分,不同位置出现的子串不算相同子串。

解析

我们考虑怎么把AABB分开求。

那么我们只要求出每个以每个点为终点的AA串个数和以每个点为起点的AA串个数,然后相邻的点乘一下求和就可以了。

下面开始就是技巧了。

考虑枚举A串的长度$L$,然后每$L$个点设置关键点。

也就是说,设置的关键点为$L,2L,3L….$。

我们发现,每一个AA串长度为$2L$,那么它一定会正好包含两个相邻的关键点。

那么我们枚举这两个关键点,考虑求出正好包含这两个关键点的串的AA串的贡献。

设我们当前枚举的左边的关键点为$pos_1$,右边的关键点为$pos_2$这个AA串左端点最左可以到$head$,最右可以到$tail$。

给个图方便理解一下吧

那么在上图(是真的丑)中,AA串整体只能在灰色区域内滑动。

容易发现,$head$一定是在合法位置内,最左边的满足$S_{[head,pos_1]}=S_{[pos_2-(pos_1-head),pos_2]}$的点。

同理,$tail$一定是在合法位置内,最右边的满足$S_{[pos_2,tail]}=S_{[pos_1-(tail-pos_2),pos_1]}$的点。

这个东西二分一下就好了,有贡献的区间就是$[head+2L-1,tail]$和$[head,tail-2L+1]$,分别对应向前和向后的AA串个数。

然后这个对于不同的$L$有多次贡献,差分即可。

外层枚举$L$的复杂度是$\sum_{L=1}^{n}\frac nL=\mathcal{O}(n\ log\ n)$的,加上二分,总复杂度就是$\mathcal O(n\ log^2\ n)$的。

注意一下边界条件就可以了。

Code

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
127
128
129
130
131
132
133
134
135
136
137
138
#define DREP(i, s, e) for(int i = s; i >= e ;i--)
#define REP(i, s, e) for(int i = s; i <= e ;i++)

#define DEBUG fprintf(stderr, "Passing [%s] in Line %d\n", __FUNCTION__, __LINE__)
#define chkmax(a, b) a = max(a, b)
#define chkmin(a, b) a = min(a, b)

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;
const int maxn = 30000 + 10;

template <typename T> inline T read()
{
T ans(0), p(1);
char c = getchar();
while (!isdigit(c))
{
if (c == '-') p = -1;
c = getchar();
}
while (isdigit(c))
{
ans = ans * 10 + c - 48;
c = getchar();
}
return ans * p;
}

int n;
char s[maxn];

template <long long base, long long MOD>
struct Hash_Table
{
long long bin[maxn], Hash[maxn];
Hash_Table()
{
bin[0] = 1;
REP(i, 1, maxn-10) bin[i] = bin[i-1] * base % MOD;
}
void calc()
{
REP(i, 1, n) Hash[i] = (Hash[i-1] * base % MOD + s[i] - 'a') % MOD;
}
long long get_val(int l, int r)
{
long long res = Hash[r] - Hash[l-1] * bin[r - l + 1] % MOD;
return (res % MOD + MOD) % MOD;
}
};
Hash_Table <23, (long long)1e9 + 7> H1;
Hash_Table <31, (long long)1e9 + 9> H2;
bool same(int l1, int r1, int l2, int r2)
{
return (r1 - l1 + 1 == r2 - l2 + 1) && H1.get_val(l1, r1) == H1.get_val(l2, r2) && H2.get_val(l1, r1) == H2.get_val(l2, r2);
}

long long pre[maxn], nex[maxn], ans;
void add(long long s[], int Start, int End, int val)
{
#ifdef CraZYali
fprintf(stderr, "%s: %d\t%d\t%d\n", s==pre?"pre":"nex",Start, End, val);
#endif
s[Start] += val;
s[End+1] += -val;
}
void calc()
{
REP(i, 1, n) pre[i] += pre[i-1];
REP(i, 1, n) nex[i] += nex[i-1];
ans = 0;
REP(i, 1, n-1) ans += pre[i] * nex[i+1];
}

int main()
{
#ifdef CraZYali
freopen("1117.in", "r", stdin);
freopen("1117.out", "w", stdout);
freopen("1117.err", "w", stderr);
#endif
int T = read<int>();
while (T--)
{
scanf("%s", s + 1);
n = strlen(s + 1);
H1.calc();H2.calc();
REP(i, 1, n) pre[i] = nex[i] = 0;
REP(L, 1, n)
for (int i = 2; i * L <= n; i++)
if (s[(i-1) * L] == s[i * L])
{
int pos1 = (i-1) * L, pos2 = i * L, l, r, head(pos1), tail(pos2), Start, End;
l = max(1, pos1 - L + 1), r = pos1;
while (l <= r)
{
int mid = l + r >> 1;
if (same(mid, pos1, pos2 - (pos1 - mid), pos2))
{
head = mid;
r = mid - 1;
}
else l = mid + 1;
}
l = pos2, r = min(n, pos2 + L - 1);
while (l <= r)
{
int mid = l + r >> 1;
if (same(pos2, mid, pos1, pos1 + (mid - pos2)))
{
tail = mid;
l = mid + 1;
}
else r = mid - 1;
}
#ifdef CraZYali
fprintf(stderr, "%d %d %d %d %d\n", L, pos1, pos2, head, tail);
#endif
Start = head, End = tail - L * 2 + 1;
if (Start <= End) add(nex, Start, End, 1);
Start = head + L * 2 - 1, End = tail;
if (Start <= End) add(pre, Start, End, 1);
}
calc();
#ifdef CraZYali
REP(i, 1, n) fprintf(stderr, "%lld%c", pre[i], i == n ? '\n' : ' ');
REP(i, 1, n) fprintf(stderr, "%lld%c", nex[i], i == n ? '\n' : ' ');
int pos1 = 3, pos2 = 6, mid = 8;
cerr << "judge: " << same(pos2, mid, pos1 - (mid - pos2), pos1)<<endl;
fputs("==========\n", stderr);
#endif
printf("%lld\n", ans);
}
return 0;
}

Notice

为什么我说是个假解呢。

因为二分那个步骤完全可以$SA+ST$来$\mathcal O(1)$解决,我有一个$log$。

那这个复杂度其实是不太对的,但是数据小啊QAQ。

到时候在补吧。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×