A.ABA and BAB(递推)
题意:
给你一个长度为
N
N
N的字符串
S
S
S,由字符A
和B
组成。
您可以按任意顺序执行以下两种类型的操作零次或多次:
- 在
S
S
S中选择一个(连续)子串
ABA
并替换为A
。 - 在
S
S
S中选择一个(连续的)子串
BAB
并替换为B
。
求出进行上述操作后,可能的字符串的数目 S S S,答案对 1 0 9 + 7 10^9+7 109+7取模。
分析:
我们从前往后每次找出极长的一段ABAB...
或BABA...
,很明显相邻两段是无法合并的,根据乘法原理计算每段可形成的形态的个数最后乘起来即可。
ABAB...
或BABA...
的形态个数我们可以用递推来预处理。设
d
p
i
dp_i
dpi表示长度为
i
i
i的ABAB...
或BABA...
的形态个数。递推式为:
d
p
i
=
d
p
i
−
2
+
1
dp_i=dp_{i−2}+1
dpi=dpi−2+1。
d
p
1
dp_1
dp1和
d
p
2
dp_2
dp2要初始化为
1
1
1。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 250005;
const int MOD = 1000000007;
char a[N];
LL dp[N];
int main() {
dp[1] = 1;
dp[2] = 1;
for (int i = 1; i < N; i++)
dp[i] = dp[i - 2] + 1;
LL n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
char lst = a[1];
LL ls = 1;
LL sum = 1;
for (int i = 2; i <= n; i++) {
if (a[i] == 'A') {
if (lst == 'B')
ls++;
else {
sum *= dp[ls];
ls = 1;
}
}
if (a[i] == 'B') {
if (lst == 'A')
ls++;
else {
sum *= dp[ls];
ls = 1;
}
}
lst = a[i];
sum %= MOD;
}
sum *= dp[ls];
sum %= MOD;
cout << sum << endl;
return 0;
}
B.Improve Inversions(构造)
题意:
给你一个 ( 1 , 2 , ⋯ , N ) (1,2,\cdots,N) (1,2,⋯,N)的排列组合 P = ( P 1 , P 2 , ⋯ , P N ) P=(P_1,P_2,\cdots,P_N) P=(P1,P2,⋯,PN)。另外,你还得到一个整数 K K K。
您可以执行下列操作零次或多次:
- 选择整数
l
l
l和
r
r
r(
1
≤
l
<
r
≤
N
1\leq l\lt r\leq N
1≤l<r≤N)。一对
(
l
,
r
)
(l,r)
(l,r)必须满足以下所有条件:
- K ≤ r − l K\leq r-l K≤r−l。
- P l > P r P_l\gt P_r Pl>Pr。
- 之前从未选择过一对 ( l , r ) (l,r) (l,r)。
- 然后,交换 P l P_l Pl和 P r P_r Pr的值。
你希望最大限度地增加操作次数。请找出一种实现方法。
分析:
先考虑当 k = 1 k=1 k=1时怎么处理,可以发现交换次数的上界就是排列的逆序对数,则考虑是否存在一种方案使得交换次数总能达到该上界。
考虑按照从小到大的顺序考虑每个数 x x x,设其在序列中初始所在的位置为 p o s x pos_x posx,则我们对于所有 j > p o s x j>pos_x j>posx∧ p j < x p_j\lt x pj<x的数均可以进行一次交换。即将后面的比 x x x小的数按照从大到小的顺序依次与位置 p o s x pos_x posx上的数进行交换,可以发现这样的交换总能进行。
由于我们是按照从小到大的顺序进行这个操作的,因此在处理后面更大的数时比它小的数的相对位置可能会发生变化,但对答案没有影响。因此就得到了一种达到上界的构造方案,推广到 k > 1 k>1 k>1的情形也是类似的,模拟地构造即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 505;
int n, k, a[N], pos[N];
vector<pair<int, int>> ans;
int main() {
cin >> n >> k;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
pos[a[i]] = i;
}
for (int i = 1; i <= n; ++i) {
for (int l = pos[i];;) {
int best = -1, num = -1;
for (int j = l + k; j <= n; ++j)
if (a[j] < a[l] && a[j] > best)
best = a[j], num = j;
if (best != -1) {
ans.push_back(pair<int, int>(l, num)), swap(a[l], a[num]);
} else
break;
}
}
cout << ans.size() << endl;
for (auto [l, r]: ans)
cout << l << " " << r << endl;
return 0;
}
C.Subsequence and Prefix Sum(动态规划)
题意:
问题陈述
给你一个长度为 N N N的整数序列 A = ( A 1 , A 2 , ⋯ , A N ) A=(A_1,A_2,\cdots,A_N) A=(A1,A2,⋯,AN)。
你要准确地执行一次下面的操作:
- 选择 A A A的一个非空子序列(不一定连续),并用它的累积和替换它。更精确地说,首先选择一个索引序列 ( i 1 , i 2 , ⋯ , i k ) (i_1,i_2,\cdots,i_k) (i1,i2,⋯,ik),使得 1 ≤ i 1 < i 2 < ⋯ < i k ≤ N 1\leq i_1\lt i_2\lt\cdots\lt i_k\leq N 1≤i1<i2<⋯<ik≤N,序列 k k k( 1 ≤ k ≤ N 1\leq k\leq N 1≤k≤N)的长度可以自由选择。然后,对于每个 j j j( 1 ≤ j ≤ k 1\leq j\leq k 1≤j≤k),用 ∑ 1 ≤ x ≤ j A i x \sum_{1\leq x\leq j}A_{i_x} ∑1≤x≤jAix替换 A i j A_{i_j} Aij的值。所有选定的索引同时进行替换。
求操作后的可能序列 A A A数量,答案对 1 0 9 + 7 10^9+7 109+7取模。
分析:
考虑 D P DP DP,定义 f i , j f_{i,j} fi,j表示处理了前 i i i个数,且此时 a i = j a_i=j ai=j的方案数。
根据操作的性质,当 j ≠ 0 j≠0 j=0时我们可以直接枚举下一个选中的位置 k k k,转移为 f k , a k + j ← f i , j f_{k,ak+j}←f_{i,j} fk,ak+j←fi,j,此时 k k k位置上的数一定会发生变化,因此是一种不同的方案。
但当 j = 0 j=0 j=0时情况就有点特殊了,此时直接枚举到的下一个位置 k k k是不会变化的,因此不能记为一种方案。因此需要在 [ i + 1 , k − 1 ] [i+1,k−1] [i+1,k−1]中找一个数 v ≠ 0 v≠0 v=0作为中转点将其贡献转给 a k a_k ak,即 f k , a k + v ← f i , j f_{k,a_k+v}←f_{i,j} fk,ak+v←fi,j。
要注意中间的这段数需要进行去重,因为选它们中值相同的位置不会改变序列本身的形态,是同质的操作。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 105;
const int M = 100 * 20 + 5;
const int MOD = 1000000007;
int n, a[N], mn, mx, f[N][M], ans;
inline void inc(int &x, int y) {
if ((x += y) >= MOD) x -= MOD;
}
int main() {
int k;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
mn += min(a[i], 0);
mx += max(a[i], 0);
}
f[0][0 - mn] = 1;
for (int i = 0; i < n; ++i)
for (int j = mn; j <= mx; ++j) {
if (!f[i][j - mn])
continue;
if (j == 0) {
set<int> s;
s.insert(a[i + 1]);
for (k = i + 2; k <= n; ++k) {
for (auto x: s) {
if (x)
inc(f[k][a[k] + x - mn], f[i][j - mn]);
}
s.insert(a[k]);
}
} else {
for (k = i + 1; k <= n; ++k)
inc(f[k][a[k] + j - mn], f[i][j - mn]);
}
}
for (int i = 0; i <= n; ++i)
for (int j = mn; j <= mx; ++j)
inc(ans, f[i][j - mn]);
cout << ans << endl;
return 0;
}
D.Division into 3(区间)
题意:
问题陈述
给你一个长度为 N N N的整数序列 A = ( A 1 , A 2 , ⋯ , A N ) A=(A_1,A_2,\cdots,A_N) A=(A1,A2,⋯,AN)。请回答下列 Q Q Q个问题。
- 第
i
i
i次查询:给你整数
L
i
L_i
Li和
R
i
R_i
Ri。请为
B
=
(
A
L
i
,
A
L
i
+
1
,
⋯
,
A
R
i
)
B=(A_{L_i},A_{L_i+1},\cdots,A_{R_i})
B=(ALi,ALi+1,⋯,ARi)解答下面的问题。
- 将 B B B分成三个非空的连续子序列。对于每个连续子序列,让我们找出其元素的最大值。求这些最大值之和的最小值。在这里,问题的限制条件迫使 B B B的长度至少为 3 3 3,因此总有至少一种方法将其分成三个非空的连续子序列。
分析:
设最终 [ l , r ] [l,r] [l,r]分裂成了三个区间 [ l 1 , r 1 ] , [ l 2 , r 2 ] , [ l 3 , r 3 ] [l_1,r_1],[l_2,r_2],[l_3,r_3] [l1,r1],[l2,r2],[l3,r3],并且 [ l , r ] [l,r] [l,r]的最大值在 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]内,那么我们令 l 2 = l 1 + 1 , r 1 = l 1 , r 2 = r 3 − 1 , l 3 = r 3 l_2=l_1+1,r_1=l_1,r_2=r_3−1,l_3=r_3 l2=l1+1,r1=l1,r2=r3−1,l3=r3,一定不会更劣,所以第一种可能的情况就是区间长度 1 , k , 1 1,k,1 1,k,1,这个可以直接 R M Q RMQ RMQ解决。
然后若 [ l , r ] [l,r] [l,r]的最大值在 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]或 [ l 3 , r 3 ] [l_3,r_3] [l3,r3]内,容易发现,后者与前者等价,只需要反转序列重做即可,所以下面考虑的是 [ l , r ] [l,r] [l,r]的最大值在 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]中的情况。
这个时候,我们令 l 3 = l 2 + 1 l_3=l_2+1 l3=l2+1, r 2 = l 2 r_2=l_2 r2=l2,这样答案也一定不会更劣,于是 l 2 = r 2 l_2=r_2 l2=r2。考虑求出这个最大值所在的位置 p o s pos pos,那么 r = r 3 ≥ l 3 = l 2 − 1 ≥ p o s r=r_3\ge l_3=l_2−1\ge pos r=r3≥l3=l2−1≥pos。
于是我们用一个指针维护 r r r,然后开一棵线段树存储每个 l 2 l_2 l2的答案,也即是 a n s l 2 = a l 2 + m a x l 2 < i ≤ r a i ans_{l_{2}}=a_{l_{2}}+max_{l_{2}\lt i\le r}a_i ansl2=al2+maxl2<i≤rai,最后查询 m i n p o s < i < r a n s i min_{pos\lt i\lt r}ans_i minpos<i<ransi即可。
这一部分直接使用单调栈维护,然后用带区间修改的线段树辅助就可以了。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 300005;
struct node {
LL l, r, id, lt, mx;
} p[N];
bool cmp(node a, node b) { return a.r < b.r; }
LL n, q, i, j, top, sta[N], a[N], ans[N], st[N][21], st2[N][21], st3[N][21], tr[N << 2];
inline LL solve(LL l, LL r) {
LL c = log2(r - l + 1);
return max(st[l][c], st[r - (1 << c) + 1][c]);
}
inline LL solve2(LL l, LL r) {
LL c = log2(r - l + 1);
if (st[l][c] >= st[r - (1 << c) + 1][c]) return st2[l][c];
else return st2[r - (1 << c) + 1][c];
}
inline LL solve3(LL l, LL r) {
LL c = log2(r - l + 1);
return min(st3[l][c], st3[r - (1 << c) + 1][c]);
}
inline void build(LL s, LL t, LL p) {
if (s == t) {
tr[p] = 0x3f3f3f3f3f3f3f3f;
return;
}
build(s, (s + t) / 2, 2 * p), build((s + t) / 2 + 1, t, 2 * p + 1);
tr[p] = min(tr[2 * p], tr[2 * p + 1]);
}
inline void add(LL x, LL c, LL s, LL t, LL p) {
if (s == t) {
tr[p] = c;
return;
}
if (x <= (s + t) / 2) add(x, c, s, (s + t) / 2, 2 * p);
else add(x, c, (s + t) / 2 + 1, t, 2 * p + 1);
tr[p] = min(tr[2 * p], tr[2 * p + 1]);
}
inline LL query(LL l, LL r, LL s, LL t, LL p) {
if (l <= s && t <= r) return tr[p];
if (l <= (s + t) / 2 && r > (s + t) / 2)
return min(query(l, r, s, (s + t) / 2, 2 * p), query(l, r, (s + t) / 2 + 1, t, 2 * p + 1));
else if (l <= (s + t) / 2) return query(l, r, s, (s + t) / 2, 2 * p);
else return query(l, r, (s + t) / 2 + 1, t, 2 * p + 1);
}
inline void solve() {
for (i = n; i >= 1; i--) {
st[i][0] = a[i], st2[i][0] = i, st3[i][0] = a[i];
for (j = 1; i + (1 << j) - 1 <= n; j++) {
if (st[i][j - 1] >= st[i + (1 << j - 1)][j - 1]) st2[i][j] = st2[i][j - 1];
else st2[i][j] = st2[i + (1 << j - 1)][j - 1];
st[i][j] = max(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
st3[i][j] = min(st3[i][j - 1], st3[i + (1 << j - 1)][j - 1]);
}
}
for (i = 1; i <= q; i++) p[i].lt = solve2(p[i].l, p[i].r) + 1;
sort(p + 1, p + q + 1, cmp);
build(1, n, 1);
for (i = 1, j = 1, top = 0; i <= q; i++) {
while (j <= p[i].r) {
while (top && a[sta[top]] <= a[j]) top--;
sta[++top] = j;
add(top, solve3(sta[top - 1], sta[top] - 1) + a[sta[top]], 1, n, 1);
j++;
}
if (p[i].lt <= p[i].r) {
LL pos = lower_bound(sta + 1, sta + top + 1, p[i].lt) - sta + 1;
if (pos <= top) ans[p[i].id] = min(ans[p[i].id], query(pos, top, 1, n, 1) + p[i].mx);
if (p[i].lt < sta[pos - 1])
ans[p[i].id] = min(ans[p[i].id], solve3(p[i].lt, sta[pos - 1] - 1) + a[sta[pos - 1]] + p[i].mx);
}
}
}
int main() {
cin >> n >> q;
for (i = 1; i <= n; i++)
cin >> a[i];
for (i = n; i >= 1; i--) {
st[i][0] = a[i];
for (j = 1; i + (1 << j) - 1 <= n; j++)
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
for (i = 1; i <= q; i++) {
cin >> p[i].l >> p[i].r;
p[i].id = i;
}
for (i = 1; i <= q; i++) {
ans[i] = solve(p[i].l + 1, p[i].r - 1) + a[p[i].l] + a[p[i].r];
p[i].mx = solve(p[i].l, p[i].r);
}
solve();
reverse(a + 1, a + n + 1);
for (i = 1; i <= q; i++) {
swap(p[i].l, p[i].r);
p[i].l = n - p[i].l + 1;
p[i].r = n - p[i].r + 1;
}
solve();
for (i = 1; i <= q; i++)
cout << ans[i] << endl;
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。