[BZOJ3439]Kpm的MC密码 题解
题目地址:BZOJ& …
May all the beauty be blessed.
题目地址:洛谷:【P1117】[NOI2016]优秀的拆分 – 洛谷、BZOJ:Problem 4650. — [Noi2016]优秀的拆分
如果一个字符串可以被拆分为 AABB 的形式,其中 A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的。
例如,对于字符串 aabaabaa,如果令 A=aab,B=a,我们就找到了这个字符串拆分成 AABB的一种方式。
一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令 A=a,B=baa,也可以用 AABB表示出上述字符串;但是,字符串 abaabaa 就没有优秀的拆分。
现在给出一个长度为 n的字符串 S,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。
以下事项需要注意:
问一个字符串所有子串中,能够将子串拆分成AABB的拆分方案总数。其中A、B是任意非空串,A=B同样合法。
输入格式:
每个输入文件包含多组数据。输入文件的第一行只有一个整数 T,表示数据的组数。保证 1≤T≤10。
接下来 T行,每行包含一个仅由英文小写字母构成的字符串 S,意义如题所述。
输出格式:
输出 T行,每行包含一个整数,表示字符串 S 所有子串的所有拆分中,总共有多少个是优秀的拆分。
输入样例#1:
4 aabbbb cccccc aabaabaabaa bbaabaababaaba
输出样例#1:
3 5 4 7
我们用 S[i,j]表示字符串 S第 i 个字符到第 j个字符的子串(从 1开始计数)。
第一组数据中,共有 3个子串存在优秀的拆分:
S[1,4]=aabb,优秀的拆分为 A=a,B=b;
S[3,6]=bbbb,优秀的拆分为 A=b,B=b;
S[1,6]=aabbbb,优秀的拆分为 A=a,B=bb。
而剩下的子串不存在优秀的拆分,所以第一组数据的答案是 3。
第二组数据中,有两类,总共 4 个子串存在优秀的拆分:
对于子串 S[1,4]=S[2,5]=S[3,6]=cccc,它们优秀的拆分相同,均为 A=c,B=c,但由于这些子串位置不同,因此要计算 3 次;
对于子串 S[1,6]=cccccc,它优秀的拆分有 2 种:A=c,B=cc 和 A=cc,B=c,它们是相同子串的不同拆分,也都要计入答案。
所以第二组数据的答案是 3+2=5。
第三组数据中,S[1,8] 和 S[4,11] 各有 2 种优秀的拆分,其中 S[1,8] 是问题描述中的例子,所以答案是 2+2=4。
第四组数据中,S[1,4],S[6,11],S[7,12],S[2,11],S[1,8] 各有 1种优秀的拆分,S[3,14] 有 2 种优秀的拆分,所以答案是 5+2=7。
对于全部的测试点,保证 1≤T≤10。以下对数据的限制均是对于单组输入数据而言的,也就是说同一个测试点下的 T组数据均满足限制条件。
我们假定 n 为字符串 S 的长度,每个测试点的详细数据范围见下表:
All n≤30000
参考资料:
我们考虑对于每个位置i,统计两个信息pre
和suf
,表示前缀i后缀为AA的个数与后缀i前缀为AA的个数。答案显然就是 \displaystyle \sum_{i=1}^n pre[i]suf[i+1]。
后缀的前缀和前缀的后缀问题显然可以用SA解决(方法参见:后缀数组(倍增)算法原理及应用 | KSkun’s Blog),但是我们需要一个统计的思路。
考虑枚举AA模式中一段A的串长k,然后在原串中每k个字符设置一个关键点,对于任意一个A串长为k的AA串,一定恰好经过两个相邻的关键点kt、k(t+1)。我们考虑求出两关键点的前缀LCS长度l和后缀LCP长度r,如果 (l+r) \geq k,则我们就找到了一个符合条件的AA串。
考虑找到的每个AA串对pre
和suf
造成的影响。设相邻的两个关键点靠前的为i,靠后的为j。能够产生AA串作为前缀的位置,这一段位置应当是[j-l+k, j+r-1]。对应示例图,我们可以发现这段区间对应的是A’这个串第k个字符到末尾,这是由于A部分此时至少要保留[i-l+1, j-l-1]这一段,也就对应A’部分的[i-l+k+1, j-l+k-1]一段。而作为后缀的位置是[i-l+1, i+r-k],原理相同。那么实际上每次的操作就是对pre
、suf
中连续一段区间+1,可以将操作差分降低复杂度。
这样一通操作下来的复杂度实际上是O(Tn \log n)的,数据范围不是很卡。
// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
typedef long long LL;
const int MAXN = 100005;
int Log2[MAXN];
inline void callog2() {
for(int i = 2; i < MAXN; i++) {
Log2[i] = Log2[i >> 1] + 1;
}
}
struct SuffixArray {
char s[MAXN];
int n, m, sa[MAXN], t[MAXN], t1[MAXN], c[MAXN], rnk[MAXN], hei[MAXN], st[MAXN][25];
inline void init() {
memset(this, 0, sizeof(SuffixArray));
}
inline void calsa() {
int *x = t, *y = t1;
for(int i = 1; i <= m; i++) c[i] = 0;
for(int i = 1; i <= n; i++) c[x[i] = s[i]]++;
for(int i = 1; i <= m; i++) c[i] += c[i - 1];
for(int i = n; i >= 1; i--) sa[c[x[i]]--] = i;
for(int k = 1; k <= n; k <<= 1) {
int p = 0;
for(int i = n - k + 1; i <= n; i++) y[++p] = i;
for(int i = 1; i <= n; i++) if(sa[i] > k) y[++p] = sa[i] - k;
for(int i = 1; i <= m; i++) c[i] = 0;
for(int i = 1; i <= n; i++) c[x[y[i]]]++;
for(int i = 1; i <= m; i++) c[i] += c[i - 1];
for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i];
std::swap(x, y);
p = x[sa[1]] = 1;
for(int i = 2; i <= n; i++) {
x[sa[i]] = (y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k])
? p : ++p;
}
if(p >= n) break;
m = p;
}
memcpy(rnk, x, sizeof(rnk));
int k = 0;
for(int i = 1; i <= n; i++) {
int j = sa[rnk[i] + 1];
if(k) k--;
if(!j) continue;
while(s[i + k] == s[j + k]) k++;
hei[rnk[i]] = k;
}
}
inline void calst() {
for(int i = 1; i <= n; i++) {
st[i][0] = hei[i];
}
for(int i = 1; i <= 20; i++) {
for(int j = 1; j + (1 << i) <= n; j++) {
st[j][i] = std::min(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]);
}
}
}
inline int querylcp(int x, int y) {
x = rnk[x]; y = rnk[y];
if(x > y) std::swap(x, y);
int t = Log2[y - x];
return std::min(st[x][t], st[y - (1 << t)][t]);
}
} saa, sab;
int T, n, pre[MAXN], suf[MAXN];
inline int lcp(int x, int y) {
return saa.querylcp(x, y);
}
inline int lcs(int x, int y) {
return sab.querylcp(n - x + 1, n - y + 1);
}
int main() {
callog2();
scanf("%d", &T);
while(T--) {
saa.init(); sab.init();
memset(pre, 0, sizeof(pre));
memset(suf, 0, sizeof(suf));
saa.m = sab.m = 256;
scanf("%s", saa.s + 1);
saa.n = sab.n = n = strlen(saa.s + 1);
for(int i = 1; i <= n; i++) {
sab.s[i] = saa.s[n - i + 1];
}
saa.calsa(); saa.calst();
sab.calsa(); sab.calst();
for(int k = 1; k <= n >> 1; k++) {
for(int i = k, j = i + k; j <= n; i += k, j += k) {
int l = std::min(lcs(i - 1, j - 1), k - 1), r = std::min(lcp(i, j), k);
if(l + r >= k) {
pre[j - l + k - 1]++; pre[j + r]--;
suf[i - l]++; suf[i + r - k + 1]--;
}
}
}
for(int i = 1; i <= n; i++) {
pre[i] += pre[i - 1];
suf[i] += suf[i - 1];
}
LL ans = 0;
for(int i = 1; i <= n; i++) {
ans += 1ll * pre[i] * suf[i + 1];
}
printf("%lld\n", ans);
}
return 0;
}
一张纸可以做s个纸飞机,现在有k个人,每个人要做n个纸飞机,而一包纸有p张,他们想买若干包然后把纸分给每个人让他们做纸飞机,求他们应该买多少包纸才能满足条件。
没什么难度,按照题目的意思求即可。答案是
[eq display=”1″] \lceil \frac{\lceil \frac{n}{s} \rceil k}{p} \rceil [/eq]
打CFR的时候看一眼1A的那种题。
// Code by KSkun, 2018/4
#include <cstdio>
#include <cmath>
int k, n, s, p;
int main() {
scanf("%d%d%d%d", &k, &n, &s, &p);
printf("%d", int(ceil(ceil(double(n) / s) * k / p)));
return 0;
}
给你一个n*n的地图,有的格子可能属于一条船,有的不属于。一条船的长度为k,体现在地图上就是横向或者纵向连续的k个是船的格子。求可能属于的船的数量最多的格子。
数据范围:n 100
枚举每个格子,然后暴力找到它可能属于的船的数量。具体来说,就是上下/左右找一通连通块,注意延伸出去不能超过k-1格不然不能把这个格子包含进去。然后答案就是这个连通块的大小减去k-1。
写了一会,由于当时CFR就写的很丑emm
// Code by KSkun, 2018/4
#include <cstdio>
#include <cmath>
#include <algorithm>
int n, k;
char mmp[105][105];
int ax, ay, ans = -1;
inline int count(int x, int y) {
if(mmp[x][y] == '#') return 0;
int cnt = 0, tcnt = 1, bcnt = 0;
for(int i = x - 1; i >= 1; i--) {
if(mmp[i][y] == '#') break;
tcnt++;
}
if(tcnt > k) tcnt = k; cnt += tcnt; tcnt = 1;
for(int i = x + 1; i <= n; i++) {
if(mmp[i][y] == '#') break;
tcnt++;
}
if(tcnt > k) tcnt = k; cnt += tcnt - 1; tcnt = 1;
bcnt += std::max(cnt - (k - 1), 0); cnt = 0;
for(int i = y - 1; i >= 1; i--) {
if(mmp[x][i] == '#') break;
tcnt++;
}
if(tcnt > k) tcnt = k; cnt += tcnt; tcnt = 1;
for(int i = y + 1; i <= n; i++) {
if(mmp[x][i] == '#') break;
tcnt++;
}
if(tcnt > k) tcnt = k; cnt += tcnt - 1; tcnt = 1;
bcnt += std::max(cnt - (k - 1), 0); cnt = 0;
return bcnt;
}
int main() {
scanf("%d%d%d%d", &n, &k);
for(int i = 1; i <= n; i++) {
scanf("%s", mmp[i] + 1);
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
int cnt = count(i, j);
if(cnt > ans) {
ans = cnt; ax = i; ay = j;
}
}
}
printf("%d %d", ax, ay);
return 0;
}
k个人想分n块糖。分糖的规则是这样的,先确定每个人每次分到的糖x,先给第一个人x个,然后第二个,一轮给完以后再轮到第一个人,直到剩下的糖数不足x,剩下的糖会直接扔掉。但是有限制每个人最多不能分到糖D次,且x也不能大于M。求第一个人分到糖的最大数目。
数据范围:n: 1e18,M: ≤n,D: 1e3
我们看到D的范围很小,因此我们可以枚举每个人分到的糖的最多次数i。我们可以根据i计算出一个x的范围,而实际上我们肯定想要最大的x,因为第一个人分到糖的数量实际上是xi。这个x很好求:
x = \lfloor \frac{n}{k(i-1)+1} \rfloor
求出x以后看一下超没超M,超了就直接设成M检查x是否能满足i的要求。然后对于x直接计算答案即可。对于一个确定的x,它的答案是xi。
CFR的时候,一没看见剩下的糖扔掉,二没看见D的范围小,而且对数据范围并不敏感,直接导致没想到枚举D。想到了以后应该是很好做的吧。
当时打了个表,发现如果不管剩下的糖扔掉这条,答案在n的两个因数之间是单峰的,写了个找因数+三分找峰,复杂度O(\sqrt{n} + \log^2 n),直接TLE。
// Code by KSkun, 2018/4
#include <cstdio>
#include <cmath>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
LL n, k, M, D;
int main() {
n = readint(); k = readint(); M = readint(); D = readint();
LL ans = 0;
for(LL i = 1; i <= D; i++) {
LL lg = log(i - 1) / log(10) + log(k) / log(10); // 会爆LL,log处理一下
if(lg > log(n) / log(10)) continue;
LL x = std::min(M, n / ((i - 1) * k + 1));
if(!x) continue;
LL ri = ceil(n / x / double(k));
if(ri != i) continue;
ans = std::max(ans, x * i);
}
printf("%I64d", (long long) ans);
return 0;
}
河宽w,一只青蛙最多能跳l这么远。给你河中每个位置的石头数,每个石头只能用一次,用完了就沉入水中了,求最多能让多少青蛙过河。
数据范围:w, l: 1e5
最开始想了个网络流的模型,拆点限流石头个数,然后从i向[i+1, i+l]中的每个石头连出边,但是这样的边数是O(n^2)的空间开不下。
其实我们来想一下,S的出边到[1, l],而[1, l]的出边到[2, l+1],依次类推,我们可以把这样一个区间内的点合起来看做最大流的限制啊!也就是说,\min_{i=l}^{w-1} \{sum_i - sum_{i-l}\}就是答案。
其实CF官解的解释是这样的:二分答案k,对于确定的k,首先当从近到远第i块石头到第i+k块的距离大于l的时候,k只青蛙是过不去的。因为最左边那只就跳不过去了,所以就可以O(n)验证确定的k。简化这个过程也能得到上面的解法。
// Code by KSkun, 2018/4
#include <cstdio>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
int w, l, a[100005];
int main() {
w = readint(); l = readint();
for(int i = 1; i < w; i++) {
a[i] = readint() + a[i - 1];
}
int ans = 1e9;
for(int i = l; i < w; i++) {
ans = std::min(ans, a[i] - a[i - l]);
}
printf("%d", ans);
return 0;
}
把n个字符串用它的一个前缀不重复地代表,求最小的代表串长度和。
数据范围:字符串总长: 1e5
如果放在Trie树上看这个问题,就是把一些word标记给提到这个节点的某个父亲的位置,且每个节点只能有一个word标记。考虑一个节点的子树内的word标记全都提前过,现在这个节点上本来就有word标记,那么这个节点的子树的提前也已经完成了,而如果没有word标记,就要从子树中选择一个提前,我们显然应该选择子树中深度较大的那个。逐子树递归完成这个操作,每个子树维护一个可并堆,最后把堆里的元素加个和就完事了。
复杂度O(n \log n)。
用了下pb_ds的可并堆,其实好像普通堆O(n \log n)合并也没事?
// Code by KSkun, 2018/4
#include <cstdio>
#include <ext/pb_ds/priority_queue.hpp>
typedef __gnu_pbds::priority_queue<int, std::less<int>,
__gnu_pbds::pairing_heap_tag> priority_queue;
const int MAXN = 100005;
int ch[MAXN][26], tot = 1;
bool wrd[MAXN];
inline void insert(char *s) {
int p = 1;
for(int i = 0; s[i]; i++) {
int c = s[i] - 'a';
if(!ch[p][c]) ch[p][c] = ++tot;
p = ch[p][c];
}
wrd[p] = true;
}
inline void dfs(int u, priority_queue &q, int dep) {
for(int c = 0; c < 26; c++) {
if(ch[u][c]) {
priority_queue p;
dfs(ch[u][c], p, dep + 1);
q.join(p);
}
}
if(!wrd[u]) q.pop();
q.push(dep);
}
int n;
char s[MAXN];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%s", s);
insert(s);
}
int ans = 0;
for(int c = 0; c < 26; c++) {
if(ch[1][c]) {
priority_queue q;
dfs(ch[1][c], q, 1);
while(!q.empty()) {
ans += q.top();
q.pop();
}
}
}
printf("%d", ans);
return 0;
}