Solution - Educational Codeforces Round 174 (Rated for Div. 2)

Educational Codeforces Round 174 (Rated for Div. 2)

A Was there an Array?

Solution

观察发现不存在101101,排除101101即可。

时间复杂度O(n)O(n)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>
using namespace std;
const int N = 1e2 + 1;
int n, B[N];
signed main(){
int t; cin >> t;
while (t--){
cin >> n;
for (int i = 2; i < n; ++i) cin >> B[i];
int flag = 1;
for (int i = 3; i < n - 1; ++i){
if (B[i] == 0 && B[i - 1] && B[i + 1]){
puts("NO");
flag = 0;
break;
}
}
if (flag) puts("YES");
}
return 0;
}

B Set of Strangers

Solution

首先发现给全图的一种颜色染色最多只需要两步。如果同种颜色存在相邻的,则起码需要两步,故需要恰好两步;否则可以一步染色。

然后最后要染成同一种颜色,故将所有颜色都直接染成这种颜色即是最优解。

注意到有一种颜色本身就是目标颜色,那么我们尽量找染色步数多的一种当作目标颜色。

时间复杂度O(nm)O(nm)

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
#include<bits/stdc++.h>
using namespace std;
const int N = 701;
int n, m, A[N][N], col[N * N];
const int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
signed main(){
int t; cin >> t;
while (t--){
cin >> n >> m;
for (int i = 1; i <= n * m; ++i) col[i] = 0;
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= m; ++j){
cin >> A[i][j];
col[A[i][j]] = 1;
}
}
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= m; ++j){
for (int k = 0; k < 4; ++k){
int nx = i + dx[k], ny = j + dy[k];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && A[nx][ny] == A[i][j]){
col[A[i][j]] = 2;
}
}
}
}
int ans = 0, maxn = 0;
for (int i = 1; i <= n * m; ++i){
ans += col[i];
maxn = max(maxn, col[i]);
}
ans -= maxn;
cout << ans << endl;
}
return 0;
}

C Beautiful Sequence

Solution

注意到长度最少为33,所以最左边数一定是11且最右边一定是33,那么也确定了中间一定全部是22。题目转化为找形如12223122\cdots 23子序列数量。

枚举所有11,再枚举所有这个数后面的33,假设中间夹了xx22,则贡献为2x12^x - 1。中间有多少22可以前缀和O(1)O(1)得到,故复杂度O(n2)O(n^2),不能通过此题。

考虑优化。设SiS_i[1,i][1, i]22的个数,假设枚举到ii下标的11jj下标的33,则答案贡献为2SjSi12^{S_j - S_i} - 1,故ii下标的11的贡献为

j>i,aj=3(2SjSi1)=j>i,aj=32Sj2Sij>i,aj=31=12Sij>i,aj=32Sjj>i,aj=31\begin{aligned} &\sum_{j > i, a_j = 3} (2^{S_j - S_i} - 1)\\ =&\sum_{j > i, a_j = 3} \frac{2^{S_j}}{2^{S_i}} - \sum_{j > i, a_j = 3} 1\\ =&\frac{1}{2^{S_i}}\sum_{j > i, a_j = 3} 2^{S_j} - \sum_{j > i, a_j = 3} 1 \end{aligned}

j>i,aj=32Sj\sum_{j > i, a_j = 3} 2^{S_j}j>i,aj=31\sum_{j > i, a_j = 3} 1都可以用前缀和维护,12Si\frac{1}{2^{S_i}}jj无关,故可以快速获得每个ii的贡献。

枚举所有ii,预处理22的幂次及其逆元,时间复杂度O(n)O(n)。代码展示的方法是O(nlogn)O(n\log 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
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 1, MOD = 998244353;
int n, A[N], sum2[N], one[N], idx1, three[N], idx3, sum3[N], num3[N];
int jc[N], inv2;
inline int fpow(int a, int b){
int x = 1;
while (b){
if (b & 1) x = (LL) x * a % MOD;
a = (LL) a * a % MOD;
b >>= 1;
}
return x;
}
signed main(){//printf("%d\n", (LL)inv2 * fpow(2, 1) % MOD);
int t; cin >> t;
inv2 = fpow(2, MOD - 2);

jc[0] = 1;
for (int i = 1; i < N; ++i) jc[i] = (LL)jc[i - 1] * 2 % MOD;
while (t--){
cin >> n;
idx1 = idx3 = 0;
for (int i = 1; i <= n; ++i){
cin >> A[i];
sum2[i] = sum2[i - 1] + (A[i] == 2);
if (A[i] == 1) one[++idx1] = i;
if (A[i] == 3) three[++idx3] = i;
}
for (int i = 1; i <= n; ++i){
sum3[i] = (sum3[i - 1] + (A[i] == 3 ? jc[sum2[i]] : 0)) % MOD;
num3[i] = num3[i - 1] + (A[i] == 3);
// printf("sum3 %d = %d\n", i, sum3[i]);
}
int ans = 0;
for (int i = 1; i <= idx1; ++i){
int now = one[i];
int sum23 = (sum3[n] - sum3[now] + MOD) % MOD;
// printf("sum23 = %d\n", sum23);
sum23 = (LL)sum23 * fpow(inv2, sum2[now]) % MOD;
// printf("inv2 = %d, sum2[now] = %d",inv2, sum2[now]);
// printf("sum2333 = %d\n", sum23);
int del = (sum23 - (num3[n] - num3[now]) + MOD) % MOD;
// printf("del = %d\n",del);
ans = (ans + del) % MOD;
// ans = (ans + ((LL)jc[sum2[n] - sum2[now]] - sum2[n] + sum2[now] - 1 + MOD) % MOD) % MOD; // 1 2
}
// for (int i = 1; i <= idx3; ++i){
// int now = three[i];
// ans = (ans + ((LL)jc[sum2[now]] - sum2[now] - 1 + MOD) % MOD) % MOD; // 2 3
// }
// ans = ((LL)ans + jc[sum2[n]] - sum2[n] - 1 - ((LL) sum2[n] * (sum2[n] - 1) % MOD * inv2) % MOD + MOD + MOD) % MOD; // 2
// ans = (ans - 1 + MOD) % MOD;
// cout << "G: ";
cout << ans << endl;
}
return 0;
}

D Palindrome Shuffle

Solution

首先,头尾对称的部分一定不会被选中翻转,可以忽略。现在研究去掉头尾对称部分的字符串。

如果左右各个字母总数相同,则一定不会挑越过中线的字母,也就是挑当前的前若干个,使剩下中间部分对称。

如果总数不相同,则一定要挑越过中线的字母。从左边第一个不对称的开始挑,然后把左半部分全部纳入,再从右半部分第一个开始往右,直到包含部分的每个字母都比右边剩余部分多。从右边开始同理,最后答案取一个min\min

时间复杂度O(n×26)O(n\times26)

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
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 1;
int num1[26], num2[26];
inline bool judge(){
for (int i = 0; i < 26; ++i){
if (num1[i] < num2[i]) return false;
}
return true;
}
signed main(){
int t; cin >> t;
while (t--){
string s; cin >> s;
memset(num1, 0, sizeof(num1));
memset(num2, 0, sizeof(num2));
for (int i = 0; (i + 1) * 2 <= s.length(); ++i){
++num1[s[i] - 'a'];
++num2[s[s.length() - 1 - i] - 'a'];
}
bool flag = true;
for (int i = 0; i < 26; ++i){
if (num1[i] != num2[i]){
flag = false;
break;
}
}
int ans = s.length();
// printf("flag = %d\n",flag);
if (flag){
int l = -1, r = s.length() / 2;
while (l < r - 1){
if (s[l + 1] == s[s.length() - l - 2]){
l++;
}else if (s[r - 1] == s[s.length() - r]){
r--;
}else{
break;
}
}
// printf("l = %d, r = %d\n",l, r);
ans = r - l - 1;
}
int l = -1, lm = s.length() / 2 - 1;
while (l < lm && s[l + 1] == s[s.length() - l - 2]) l++;
int i;
for (i = 0; i <= l; ++i){
num1[s[i] - 'a']--;
num2[s[i] - 'a']--;
}
i = lm + 1;
while (i < s.length() && !judge()){
num1[s[i] - 'a']++;
num2[s[i] - 'a']--;
++i;
}
ans = min(ans, i - l - 1);

reverse(s.begin(), s.end());
l = -1, lm = s.length() / 2 - 1;
while (l < lm && s[l + 1] == s[s.length() - l - 2]) l++;
memset(num1, 0, sizeof(num1)), memset(num2, 0, sizeof(num2));
for (int i = l + 1; (i + 1) * 2 <= s.length(); ++i){
++num1[s[i] - 'a'];
++num2[s[s.length() - 1 - i] - 'a'];
}
i = lm + 1;
while (i < s.length() && !judge()){
num1[s[i] - 'a']++;
num2[s[i] - 'a']--;
++i;
}
ans = min(ans, i - l - 1);
cout << ans << endl;
}
return 0;
}

/*
1
eteffeteefekfektetee

*/

E A, B, AB and BA

Solution

发现A\textrm{A}B\textrm{B}AB\textrm{AB}BA\textrm{BA}好用,所以要优先填入AB\textrm{AB}BA\textrm{BA},剩下用A\textrm{A}B\textrm{B}填充。

假设最后需要ppA\textrm{A}qqB\textrm{B},则需要AB\textrm{AB}BA\textrm{BA}的总数是w=max{pa,qb,0}w = \max\{p - a, q - b, 0\}。如果wwpp或者qq还要大,那么就是不可能的。否则,我们尝试填入AB\textrm{AB}BA\textrm{BA}

  1. AB\textrm{AB}BA\textrm{BA}只能填在AB\textrm{AB}交替的时候,我们可以记录所有AB\textrm{AB}连续交替的区间;
  2. 对于ABAAB\textrm{ABA}\cdots \textrm{AB},如果只填AB\textrm{AB},可以比混着填AB\textrm{AB}BA\textrm{BA}多填一个,这种称为AA型;对于ABABA\textrm{ABA}\cdots \textrm{BA}BABAB\textrm{BAB}\cdots \textrm{AB},任意混着填都一样多,这种称为ABAB型;对于BABBA\textrm{BAB}\cdots \textrm{BA},如果填BA\textrm{BA},可以比混着填AB\textrm{AB}BA\textrm{BA}多填一个,这种称为BB型;
  3. 我们显然更想要让AA型用来填A\textrm{A}BB型用来填B\textrm{B},剩下用ABAB型填充。为了使损失最小,我们可以按长度从小往大填AA型和BB型,溢出再减11化为ABAB型。

故我们模拟以上操作,时间复杂度O(nlogn)O(n\log 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
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 1;
int n, a, b, ab, ba, p, q, w;
int A[N], B[N], C, idxa, idxb;
signed main(){
int t; cin >> t;
while (t--){
string s; cin >> s;
n = s.length();
cin >> a >> b >> ab >> ba;
p = q = 0;
for (int i = 0; i < n; ++i){
if (s[i] == 'A') ++p;
else ++q;
}
if (p <= a && q <= b){
cout << "YES" << endl;
continue;
}
w = max(p - a, q - b);
// printf("p = %d, q = %d, w = %d\n", p, q, w);
if (ab + ba < w || w > p || w > q){
cout << "NO" << endl;
continue;
}
idxa = idxb = C = 0;
int len = 0, start = -1;
for (int i = 1; i < n; ++i){
if (s[i] == s[i - 1]){
// printf("i = %d, len = %d, start = %d\n",i,len,start);
if (start != -1){
if (len & 1){
C += len >> 1;
}else if (start == 0){
A[++idxa] = len >> 1;
}else{
B[++idxb] = len >> 1;
}
start = -1;
}
}else{
if (start == -1){
len = 2;
start = (s[i - 1] == 'B');
}else{
++len;
}
}
}
if (start != -1){
if (len & 1){
C += len >> 1;
}else if (start == 0){
A[++idxa] = len >> 1;
}else{
B[++idxb] = len >> 1;
}
}
sort(A + 1, A + 1 + idxa, [](int x, int y){return x > y;});
sort(B + 1, B + 1 + idxb, [](int x, int y){return x > y;});
// for (int i = 1; i <= idxa; ++i) printf("A %d = %d\n", i, A[i]);
// for (int i = 1; i <= idxb; ++i) printf("B %d = %d\n", i, B[i]);
// printf("C = %d\n", C);
while (idxa){
if (A[idxa] <= ab){
ab -= A[idxa];
w -= A[idxa];
--idxa;
}else{
A[idxa] -= ab;
w -= ab;
ab = 0;
break;
}
}
while (idxb){
if (B[idxb] <= ba){
ba -= B[idxb];
w -= B[idxb];
--idxb;
}else{
B[idxb] -= ba;
w -= ba;
ba = 0;
break;
}
}
// printf("ab = %d, ba = %d, w = %d\n", ab, ba, w);
for (int i = 1; i <= idxa; ++i) C += A[i] - 1;
for (int i = 1; i <= idxb; ++i) C += B[i] - 1;
w -= min(C, ab + ba);
if (w <= 0){
cout << "YES" << endl;
}else{
cout << "NO" << endl;
}
}
}/*
1
AABABBABABAAABAABBABBABABABAABAAB
5 2 4 9


*/

F Graph Inclusion

Solution

首先发现每次查询的答案就是(AB)的连通块个数A的连通块个数(A\bigcup B)\text{的连通块个数}-A\text{的连通块个数}。故我们尝试维护两个图,支持动态插入边、删除边、查询连通块个数。(然而我并不会qwq)

通过学习题解,我们得到了一个经典的离线做法:记录每条边的存在时间,按照时间轴建立线段树,将每个存在时间的区间分割成线段树上O(logq)O(\log q)个区间;然后我们遍历整个线段树,每次走到一个新节点或者回溯一步,对应的操作为将当前节点所有边关系加入并查集将当前节点所有边关系移出并查集。虽然移出并查集这个操作是不能实现的(可能?),但由于是回溯操作,所以我们只需要撤销最后若干个操作,这个操作是可以通过非路径压缩的并查集实现的(甚至不用主席树,多么好的算法)。每次到根节点记录答案就好了。

时间复杂度O(nlognlogq)O(n\log n\log q)

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
#include<bits/stdc++.h>
using namespace std;
const int N = 4e5 + 1;
int n, q, fa[N], sz[N], line[N], idx, num[N], ans, num2[N];
bool opt[N]; int px[N], py[N];
struct Edge{
int x, y;
Edge(int a, int b){
x = a;
y = b;
}
bool operator < (const Edge& other) const{
return x == other.x ? y < other.y : x < other.x;
}
};
vector<Edge> T[N << 2];
map<Edge, int> mp;
int find(int x){
return (x == fa[x] ? x : find(fa[x]));
}
inline void merge(int x, int y){
int fx = find(x), fy = find(y);
if (fx == fy) return line[++idx] = 0, void();
if (sz[fx] > sz[fy]) fx ^= fy ^= fx ^= fy, x ^= y ^= x ^= y;
ans--;
fa[fx] = fy;
line[++idx] = fx;
sz[fy] += fx;
}
inline void rollback(){
int x = line[idx--];
if (!x) return;
ans ++;
sz[fa[x]] -= sz[x];
fa[x] = x;
}
void Add(int k, int l, int r, int le, int ri, Edge e){
if (l >= le && r <= ri) return T[k].push_back(e);
int mid = l + r >> 1;
if (mid >= le) Add(k << 1, l, mid, le, ri, e);
if (mid < ri) Add(k << 1 | 1, mid + 1, r, le, ri, e);
}
void Calc(int k, int l, int r){
for (int i = 0; i < T[k].size(); ++i){
Edge now = T[k][i];
merge(now.x, now.y);
}
// printf("k = %d, l = %d, r = %d, ans = %d\n\n", k, l, r, ans);
if (l == r) num[l] = ans;
else{
int mid = l + r >> 1;
Calc(k << 1, l, mid);
Calc(k << 1 | 1, mid + 1, r);
}
for (int i = 0; i < T[k].size(); ++i){
rollback();
}
}
void clear(int k, int l, int r){
if (l == r) T[k].clear();
int mid = l + r >> 1;
clear(k << 1, l, mid);
clear(k << 1 | 1, mid + 1, r);
}
inline int read(){
int x = 0; char ch = getchar();
while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return x;
}
inline bool get(){
char ch = getchar();
while (ch != 'A' && ch != 'B') ch = getchar();
return ch == 'A';
}
signed main(){
n = read(), q = read();
// printf("n, q = %d %d\n",n,q);
for (int i = 1; i <= n; ++i) fa[i] = i, sz[i] = 1;
for (int i = 1; i <= q; ++i){
opt[i] = get(), px[i] = read(), py[i] = read();
if (px[i] > py[i]) px[i] ^= py[i] ^= px[i] ^= py[i];
}//exit(0);
// A
for (int i = 1; i <= q; ++i){
if (opt[i]){
Edge now = Edge(px[i], py[i]);
int pre = mp[now];
if (!pre) mp[now] = i;
else{
Add(1, 1, q, pre, i - 1, now);
mp.erase(now);
}
}
}
for (auto it = mp.begin(); it != mp.end(); ++it){
Edge now = (*it).first; int pre = (*it).second;
Add(1, 1, q, pre, q, now);
}
// exit(0);
mp.clear();
ans = n;
Calc(1, 1, q);
for (int i = 1; i <= q; ++i) num2[i] = num[i];
for (int i = 1; i <= q; ++i){
if (!opt[i]){
Edge now = Edge(px[i], py[i]);
int pre = mp[now];
if (!pre) mp[now] = i;
else{
Add(1, 1, q, pre, i - 1, now);
mp.erase(now);
}
}
}
for (auto it = mp.begin(); it != mp.end(); ++it){
Edge now = (*it).first; int pre = (*it).second;
Add(1, 1, q, pre, q, now);
}
mp.clear();
ans = n;
Calc(1, 1, q);
for (int i = 1; i <= q; ++i){
printf("%d\n", num2[i] - num[i]);
}
return 0;s
}