Codeforces Round 1082 Div.1 做题记录
Codeforces Round 1082 Div.1
| 编号 | CF2201 |
|---|---|
| 赛时解决 | A1,A2,B |
| 赛后订题 | A1,A2,B,C,D |
Solution
A1
初始的 \(m\) 个数把序列划分为了 \(m\) 段,这 \(m\) 个段之间是相互不干扰的。
因此考虑进入下一段的条件:
- 出现了一个数 \(x\),满足 \(x-1\) 在段中 \(x\) 前未出现。
- \(a_i - a_{i-1} > 1\),因为顺序是不对的。
因此用 map 解决存在性问题,注意每进入新的一段,需要清空 map。
A2
同样上面的思路,考虑会对多少个区间产生影响。我们对上面的两种情况考虑:
- 出现 \(x\) 但 \(x-1\) 在段中 \(x\) 前未出现。
- 考虑 \(x\) 下标为 \(i\),\(x-1\) 上次出现位置为 \(j\),那么所有 \(l \in (j,i],r \in [i,n]\) 的区间答案都会 \(+1\)。
- \(ans \gets ans + (i-j) \times (n-i+1)\)。
- \(a_i - a_{i-1} > 1\)。
- 所有 \(l \in [1,i],r \in [i,n]\) 的区间答案都会 \(+1\)。
- \(ans \gets ans + i \times (n-i+1)\)。
如果两个条件同时满足呢?注意到 \(r\) 的取值范围相同,所以选 \(l\) 取值范围更大的(一定是后者)即可。
B
有解的 \(k\) 的下界一定是 \(n\),上界显然不会超过 \(2n-1\)。
但是上界会达到 \(2n-1\) 吗?考虑 \(k = 2n-1\) 时应该是第二个数分别取到 \([2,n]\)。
因此产生一种思路,构造 1 2 3 ... n-1 n 1 2 3 ... n-1 n 的方案。
不过这样是有问题的,因为 1 2 完了之后会 3 4,也就是上界只会达到 \(\lceil \frac{3}{2}n \rceil\)。
发现 1 2 3 1 4 2 5 3 6 4 ... n-2 n n-1 n 的构造是很好的,并且两两分组后,除了第一组每一组都会轮两次,恰好 \(2n-1\)。
确定了 \(k\) 当且仅当 \(k \in [n,2n)\) 时有解,我们考虑构造方案。
已经得到了 \(k\) 取上界时的构造,其他的就基本一样了。
我们考虑 \(k\) 每减少 \(1\),就让结尾有一个形如 i i 的东西。这样前面的构造方式不变,就可以把整个序列描述为一个长为 \(m\) 的共 \(2m-1\) 轮的构造后面,拼接了 \(n-m\) 个两两相同的数字。
除了 \(n = k\) 特判掉,其他的容易得到 \(m = k-n\)。然后钦定开头为 1,2,接下来第 \(i\) 组就拼上 i-1,i+1 即可。
最后会存在多的两个 m-1 m,直接拼上就好了。
C
DP 好题!
题意:给定合法括号串(长度为 \(n\))。选取括号串的一个子序列(不连续)并把这个子序列向右循环移动一位,括号串依旧合法,求这样的子序列个数。
没有思路的时候先做性质分析!
首先读了题目应该想到什么:对于合法括号串,记 ( 为 \(+1\),) 为 \(-1\),则括号串的任意前缀和 \(\ge 0\)。
这个条件同时是合法括号串的充要条件。考虑一个子序列向右循环移动后,前缀和的变化。
我们不妨记子序列为 \(A,B,C\),那么括号序不妨写成 \(A\dots B \dots C\)。
我们记 \(A,B,C\) 三处的括号序权值(\(+1\) 或 \(-1\))分别为 \(v_A,v_B,v_C\),那么考虑循环移位后的变化量:
- 对于 \(AB\) 段来说:\(\Delta v_{AB} = v_C - v_A\)。
- 对于 \(BC\) 段来说:\(\Delta v_{BC} = (v_A+v_C) - (v_A+v_B) = v_C - v_B\)。
由此我们不难发现,循环移位后一段内的前缀和变化量就是子序列结尾的权值减去该段左端的权值。
又因为权值只能是 \(+1\) 或 \(-1\),因此我们考虑做分讨。
| 左端权值 / 结尾权值 | \(+1\) | \(-1\) |
|---|---|---|
| \(+1\) | \(\Delta = 0\),段内一定也合法 | \(\Delta = +2\),段内一定也合法 |
| \(-1\) | \(\Delta = -2\),段内可能不合法 | \(\Delta = 0\),段内一定也合法 |
综上所述,我们只需要对段左端为 (,结尾为 ) 的情况思考即可。
我们考虑如何计算结尾为 ) 的合法子序列数量,因为如果结尾是 ( 的话,除了结尾前面可以随便选。
那么结尾为 ) 的合法子序列数量考虑转移得到。也就是我们考虑在哪些子序列后面接上一个 ) 是合法的。
考虑 DP,设 \(dp[i]\) 表示对于前缀 \(1 \sim i-1\),其中子序列在结尾处拼上一个 ) 依旧合法的方案数。
那么 \(dp[i]\) 的转移将分为两部分:
- 若 \(s_j = -1\),也就是
),此时直接拼就可以。因为对于左端为(,结尾为)的情况本身就是合法的了。可以直接从 \(dp[j]\) 转移。 - 否则,需要满足 \([j,i)\) 中所有括号序前缀和都 \(\ge 2\),这样左端为
(,结尾为)换过去之后,前缀和都会 \(-2\)。这个对于 \(j\) 之前是合法的,\(j\) 及其以后不一定,所以需要以此作为转移条件。
最后进行简单优化,时间复杂度即可降到 \(O(n)\)。
如果 \(s_i = +1\) 的话,用 \(2^{i-1}\) 累计答案;否则 \(dp[i]\) 累计答案。
#include <bits/stdc++.h>
using namespace std;
const int N = 300005;
const int mod = 998244353;
int t,n,p[N],dp[N],ans,g[N];
int sum1,sum2;
string s;
int main()
{
cin >> t;
while (t--)
{
cin >> n;
cin >> s;
s = '#' + s;
sum1 = sum2 = ans = 0;
p[0] = 1, g[0] = 0;
for (int i=1;i<=n;i++)
{
p[i] = 1ll * p[i-1] * 2 % mod;
g[i] = g[i-1] + (s[i] == '(' ? 1 : -1);
}
for (int i=1;i<=n;i++)
{
dp[i] = (sum1 + sum2 + 1) % mod;
if (s[i] == '(')
{
ans = (ans + p[i-1]) % mod;
if (g[i] >= 2) sum1 = (sum1 + dp[i]) % mod;
}
else
{
sum2 = (sum2 + dp[i]) % mod;
ans = (ans + dp[i]) % mod;
}
if (g[i] < 2) sum1 = 0;
}
cout << ans << endl;
}
return 0;
}
D
不想概括题意了。
第一问,会发现当 \(i,j\) 满足 \(a_i = a_j\) 时,\(k = j-i+1\) 最优。
为什么呢?因为 \(k\) 最大的时候,一定可以分成左中右三段。左右两段长度如果大于 \(1\),那么就一定存在一个数字,满足左右两边这个数字的位置的 \(k\) 不小于此时的 \(k\)。
因此第一问就很平凡了,我们直接开权值上的 set 来维护每个权值的位置,获得极差即可。
最终的答案就是最大极差。具体做法是把极差插入到 multiset 里就行。
第二问,就会出现连续的段的情况了。
我们设一段长度为 \(len\),如果对于这一段,在它后面 \(k\) 个位置处也恰好有相同的一段,那么这整体的贡献就应该是 \(C_{len+1}^2\),因为这一段中的每个位置还有后面的一个位置都可以做起点。
所以维护连续段即可,同样对于每个 \(k\) 都开一个 set,然后类似颜色段均摊,有添加和删除操作,注意段的合并与分裂即可。
特别注意 set 里找不到的情况!特判掉!

浙公网安备 33010602011771号