Codeforces Global Round 1题解报告
A. Parity
题意
模2意义下的秦九韶。
题解
模2意义下的秦九韶。
代码
#include<bits/stdc++.h>
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int main() {
int b = ri(), k = ri(), n = 0;
for(int i = 1;i <= k; ++i)
n = (n * b + ri()) & 1;
puts(n ? "odd" : "even");
return 0;
}
B. Tape
题意
给你一条长度为 m m m的绳子,上面有 n n n处断点,利用要最多 k k k条胶带覆盖这些断点,最小化长度。
题解
取前 k k k大的断点间隔即可。记得掐头去尾。
代码
#include<bits/stdc++.h>
const int N = 1e5 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int n, m, k, b[N];
int main() {
n = ri(); m = ri(); k = ri();
for(int i = 0;i < n; ++i)
b[i] = ri();
int All = b[n - 1] - b[0] + 1, Sum = 0;
for(int i = n - 1; i; --i)
b[i] -= b[i - 1] + 1;
std::sort(b + 1, b + n);
for(int i = 1; i < k && i < n; ++i)
Sum += b[n - i];
printf("%d\n", All - Sum);
return 0;
}
C. Meaningless Operations
题意
f ( a ) = max 0 < b < a g c d ( a ⊕ b , a & b ) f(a)= \max_{0<b<a}gcd(a⊕b,a \& b) f(a)=max0<b<agcd(a⊕b,a&b),多组询问 a a a,求 f ( a ) f(a) f(a)
题解
如果
a
≠
2
n
−
1
a\neq 2^n-1
a̸=2n−1,将
a
a
a拆位考虑,
1
⊕
0
=
1
,
1
&
0
=
0
;
0
⊕
1
=
1
,
0
&
1
=
0
1⊕0=1,1 \& 0 = 0;0⊕1=1,0\& 1 = 0
1⊕0=1,1&0=0;0⊕1=1,0&1=0,这样的话构造出来的就是
a
⊕
b
=
111
⋯
1
1
(
2
)
,
a
&
b
=
0
a⊕b=111\cdots 11_{(2)},a\& b = 0
a⊕b=111⋯11(2),a&b=0,
f
(
a
)
f(a)
f(a)在此时最大。
可是当
a
=
2
n
−
1
a= 2 ^n-1
a=2n−1是,构造出来的
b
=
0
b=0
b=0,不符合题意。考场上当然直接打表啦。
题解给的方法是,此时相当于是
g
c
d
(
2
x
−
1
−
b
,
b
)
=
g
c
d
(
2
x
−
1
,
b
)
gcd(2^x-1-b,b)=gcd(2^x-1,b)
gcd(2x−1−b,b)=gcd(2x−1,b),就是找
2
x
−
1
2^x-1
2x−1的最大因子。
代码
#include<bits/stdc++.h>
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int bin[30];
const int biao[] = {1,1,5,1,21,1,85,73,341,89,1365,1,5461,4681,21845,1,87381,1,349525,299593,1398101,178481,5592405,1082401};
int main() {
bin[0] = 1;
for(int i = 1;i <= 25; ++i)
bin[i] = bin[i - 1] << 1;
int q = ri();
for(;q--;) {
int a = ri(), mx;
for(int i = 24;~i; --i)
if(a >= bin[i]) {
mx = i + 1;
break;
}
if(bin[mx] - 1 == a) printf("%d\n", biao[mx - 2]);
else printf("%d\n", bin[mx] - 1);
}
return 0;
}
D. Jongmah
题意
从一个序列里面挑出尽量多的三元组,满足要么三个数相同要么三个数连续。
题解
考虑如果某个连续型三元组超过了三个,可以直接转化成相同型的三元组,所以存在最优解使得完全相同的连续型三元组不超过两个,于是同一种数至多有六个用于组成连续型三元组。排序之后状压前面两个数值的用于组成连续三元组的情况即可。
代码
#include<bits/stdc++.h>
const int N = 1e6 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int n, m, Ans, c[N], dp[2][49];
void Up(int &a, int b) {a = std::max(a, b);}
int main() {
n = ri(); m = ri();
for(int i = 1;i <= n; ++i)
++c[ri()];
memset(dp, -0x3f, sizeof(dp));
dp[0][0] = 0; int *g = dp[0], *f = dp[1];
for(int i = 1;i <= m; ++i, std::swap(f, g)) {
memset(f, -0x3f, sizeof(dp[0]));
for(int c1 = 0;c1 < 7; ++c1)
for(int c2 = 0; c2 < 7; ++c2) {
int mx = std::min(std::min(c1, std::min(c2, c[i])), 2);
for(int t = 0;t <= mx; ++t) {
int re = c[i] - t;
if(re < 3) {
Up(f[(c2 - t) * 7 + re], g[c1 * 7 + c2] + t);
}
else if(re < 6) {
Up(f[(c2 - t) * 7 + re], g[c1 * 7 + c2] + t);
Up(f[(c2 - t) * 7 + re - 3], g[c1 * 7 + c2] + t + 1);
}
else {
int rre = re % 3 + 3, tm = re / 3 - 1;
Up(f[(c2 - t) * 7 + rre], g[c1 * 7 + c2] + t + tm);
Up(f[(c2 - t) * 7 + rre - 3], g[c1 * 7 + c2] + t + tm + 1);
}
}
}
}
int mx = 0;
for(int i = 0;i < 49; ++i)
mx = std::max(mx, g[i]);
printf("%d\n", mx);
return 0;
}
E. Magic Stones
题意
给一个序列,可以进行任意进行以下操作, c i ′ = c i − 1 + c i + 1 − c i c_i'=c_{i-1}+c_{i+1}-c_i ci′=ci−1+ci+1−ci,求是否可以使一个序列变成另一个序列。
题解
哎这种题就是想到就会,没想到就没救的那种题。早知道就直接跳了,害得我那么简单的
F
F
F没做。
差分完之后这个操作就是交换。。。然后直接排序即可。
代码
#include<bits/stdc++.h>
const int N = 1e5 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int n, c[N], t[N];
int main() {
n = ri();
for(int i = 0;i < n; ++i)
c[i] = ri();
for(int i = 0;i < n; ++i)
t[i] = ri();
if(c[0] != t[0]) return puts("No"), 0;
for(int i = n - 1; i; --i)
c[i] -= c[i - 1];
for(int i = n - 1; i; --i)
t[i] -= t[i - 1];
std::sort(c + 1, c + n);
std::sort(t + 1, t + n);
for(int i = 1;i < n; ++i)
if(t[i] != c[i])
return puts("No"), 0;
puts("Yes");
return 0;
}
F. Nearest Leaf
题意
给你一个按照dfs序建立的树,问距离某个点dfs序在某个区间的距离最近的叶子。
题解
出栈入栈序记一下,询问离线,进入子树的时候把边的贡献在子树内减掉,在子树外加上,出子树的时候回溯一下即可。询问用线段树。
代码
尝试了一下zkw,感觉不错。
#include<bits/stdc++.h>
const int N = 5e5 + 10, Nt = 1 << 20;
typedef long long LL;
const LL inf = 1e18;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int l[N], r[N], pr[N], nx[N], w[N], m, n, Q;
LL T[Nt], ans[N], D[N];
struct Data {int l, r, id;};
std::vector<Data>q[N];
void Dfs1(int u) {
l[u] = r[u] = u;
for(int i = pr[u];i; i = nx[i]) {
D[i] = D[u] + w[i];
Dfs1(i);
l[u] = std::min(l[u], l[i]);
r[u] = std::max(r[u], r[i]);
}
if(!pr[u])
T[u + m] = D[u];
else
T[u + m] = inf;
}
void Up(int s) {LL A = std::min(T[s], T[s ^ 1]); T[s] -= A; T[s ^ 1] -= A; T[s >> 1] += A;}
void Add(int s, int t, int x) {
if(s > t) return ;s += m, t += m;
if(s == t) {
for(T[s] += x;s > 1; s >>= 1) Up(s);
return ;
}
for(T[s] += x, T[t] += x;s ^ t ^ 1; s >>= 1, t >>= 1) {
if(~s & 1)
T[s ^ 1] += x;
if(t & 1)
T[t ^ 1] += x;
Up(s); Up(t);
}
for(;s > 1; s >>= 1)
Up(s);
}
LL Query(int s, int t) {
LL l = 0, r = 0;
if(s == t) {
for(s += m;s; s >>= 1) l += T[s];
return l;
}
for(s += m, t += m;s ^ t ^ 1; s >>= 1, t >>= 1) {
l += T[s]; r += T[t];
if(~s & 1)
l = std::min(l, T[s ^ 1]);
if(t & 1)
r = std::min(r, T[t ^ 1]);
}
l = std::min(l + T[s], r + T[t]);
for(;s >>= 1;) l += T[s];
return l;
}
void Dfs2(int u) {
for(Data x : q[u])
ans[x.id] = Query(x.l, x.r);
for(int i = pr[u]; i; i = nx[i]) {
Add(l[i], r[i], -w[i]);
Add(1, l[i] - 1, w[i]);
Add(r[i] + 1, n, w[i]);
Dfs2(i);
Add(l[i], r[i], w[i]);
Add(1, l[i] - 1, -w[i]);
Add(r[i] + 1, n, -w[i]);
}
}
int main() {
n = ri(); Q = ri();
for(m = 1;m <= n + 1; m <<= 1) ;
for(int i = 2, u;i <= n; ++i)
u = ri(), nx[i] = pr[u], pr[u] = i, w[i] = ri();
Dfs1(1);
for(int i = m - 1; i; --i)
T[i] = std::min(T[i << 1], T[i << 1 | 1]), T[i << 1] -= T[i], T[i << 1 | 1] -= T[i];
for(int i = 1;i <= Q; ++i) {
int v = ri(), l = ri(), r = ri();
q[v].push_back((Data){l, r, i});
}
Dfs2(1);
for(int i = 1;i <= Q; ++i)
printf("%lld\n", ans[i]);
return 0;
}
G. Tree-Tac-Toe
题意
树上三子棋,初始有白点。
题解
考后自己yy了一个神奇的做法。首先显然不可能黑胜,接下来大概就是大分类一下有白点的情况,然后枚举放的第一个白点和黑点的位置,巨烦。
看了一下标解我惊了。
想考虑没有白点的情况。如果某个点度数大于3,直接白胜,考虑度数为3的点,如果与它相邻有超过两个点不是叶子,那么白胜。剩下的情况只有以下几种:
后两种都只能平局,第一种判奇偶性即可(白棋间隔着放)
代码
#include<bits/stdc++.h>
const int N = 1e6 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
std::vector<int>G[N];
void add(int u, int v) {G[u].push_back(v);}
void adds(int u, int v) {add(u, v); add(v, u);}
int tot; char s[N];
bool Work() {
for(int i = 1;i <= tot; ++i)
G[i].clear();
int n = tot = ri(), lf = 0;
for(int i = 2;i <= n; ++i)
adds(ri(), ri());
scanf("%s", s + 1);
for(int i = 1;i <= n; ++i)
if(s[i] == 'W')
adds(i, ++tot), adds(tot, tot + 1), adds(tot, tot + 2), tot += 2;
for(int i = 1;i <= tot; ++i)
if(G[i].size() >= 4) return true;
else if(G[i].size() == 1) ++lf;
for(int i = 1;i <= tot; ++i)
if(G[i].size() == 3) {
int cnt = 0;
for(int j = 0;j < 3; ++j)
cnt += G[G[i][j]].size() >= 2;
if(cnt >= 2) return true;
}
if(lf <= 3) return false;
return tot & 1;
}
int main() {
for(int T =ri();T--;)
puts(Work() ? "White" : "Draw");
return 0;
}
H. Modest Substrings
题意
构造一个由数字构成的长度为 n n n的字符串,最大化其“合适的”子串的个数。其中“合适”定义为:1.没有前导0;2.其数值在 l , r l,r l,r之间
题解
有一种神奇的操作叫做“正则表达式”。
我们考虑如何表示数值
120
−
129
120-129
120−129。
一种简单的方式是列举
120
−
129
120 -129
120−129之间的所有数值。
但是正则表达式中,有一个符号
[
a
−
b
]
[a-b]
[a−b],表示匹配单个字符,这个字符可以是
a
−
b
a-b
a−b中的任何字符。
比如
[
0
−
9
]
[0-9]
[0−9]就可以匹配
0
−
9
0-9
0−9十个字符。
于是我们可以用
12
[
0
−
9
]
12[0-9]
12[0−9]来表示
120
−
129
120-129
120−129。
那么如何表示
120000
−
129999
120000-129999
120000−129999?
一种办法是,
12
[
0
−
9
]
[
0
−
9
]
[
0
−
9
]
[
0
−
9
]
12[0-9][0-9][0-9][0-9]
12[0−9][0−9][0−9][0−9]
但是这样要重复
4
4
4次,很烦。
正则表达式有一种神奇的方法,就是
x
{
e
}
x\{e\}
x{e},表示将
x
x
x单位字符重复
e
e
e次。
于是
120000
−
129999
120000-129999
120000−129999就变成了
12
[
0
−
9
]
{
4
}
12[0-9]\{4\}
12[0−9]{4}
这个时候考虑如何表示在
l
l
l和
r
r
r之间的所有字符。
设
l
l
l为
a
1
a
2
⋯
a
n
a_1a_2\cdots a_n
a1a2⋯an
那么
x
≥
l
x\ge l
x≥l 可以表示为
a
1
⋯
a
k
B
[
0
−
9
]
{
n
−
k
}
(
B
>
a
k
)
a_1\cdots a_kB[0-9]\{n-k\}(B> a_k)
a1⋯akB[0−9]{n−k}(B>ak)
和
[
1
−
9
]
[
0
−
9
]
{
e
}
(
e
≥
n
)
[1-9][0-9]\{e\}(e\ge n)
[1−9][0−9]{e}(e≥n)
也就是,考虑如果某个和
l
l
l位数相同的数,那么我们可以贴着
l
l
l走一段,然后再下一位放一个大于当前位的数,然后再瞎放,再加上位数大于
l
l
l的数。
l
l
l和
r
r
r两个限制加上去的处理方法是类似的。
我们发现由于
[
0
−
9
]
{
e
}
[0-9]\{e\}
[0−9]{e}的使用,大大缩减了合法数的量,事实上这样的状态不超过
O
(
80
0
2
)
O(800^2)
O(8002),因为只需要扔到
T
r
i
e
Trie
Trie树里,平方是因为
{
e
}
\{e\}
{e}的个数最大是
800
800
800。
我们发现其实
[
0
−
9
]
{
e
}
[0-9]\{e\}
[0−9]{e}这个结构很特殊,我们称之为“通配符”,对于每个
T
r
i
e
Trie
Trie,我们直接在每个节点上记录一个数组来表示这个符号即可。
剩下来考虑匹配。
我们发现任意一个合法的串都会切仅会被一个上面的正则表达式包含。
设
f
i
,
u
f_{i,u}
fi,u表示放了
i
i
i个数,走到了
T
r
i
e
Trie
Trie上的
u
u
u。
我们发现,当前节点的所有
e
e
e不大于
n
−
i
n-i
n−i的通配符都会对答案产生
1
1
1的贡献。因为不管后面放什么都一定会匹配到。同时,对这棵
T
r
i
e
Trie
Trie建立
A
C
AC
AC自动机。
f
a
i
l
fail
fail链上的所有点的通配符都会对答案产生
1
1
1的贡献。
于是我们预处理
f
a
i
l
fail
fail链,把儿子的贡献加到父亲上,然后跑一遍
D
p
Dp
Dp即可。
神奇!
代码
#include<bits/stdc++.h>
const int N = 2e4 + 10, M = 2e3 + 5;
int n, m, sz, len, ch[N][10], q[N], fa[N], g[N][M], f[M][N];
bool ok[M][N]; char s[M], t[M];
bool Can(int i, int u, int k) {return f[i][u] == f[i + 1][ch[u][k]] - g[ch[u][k]][len - i - 1];}
int Tra(int u, int x) {return ch[u][x] ?: ch[u][x] = ++sz;}
void Up(int &a, int b) {a = std::max(a, b);}
int main() {
scanf("%s", s + 1); n = strlen(s + 1);
scanf("%s", t + 1); m = strlen(t + 1);
scanf("%d", &len);
for(int i = 1;i <= n; ++i) s[i] -= '0';
for(int i = 1;i <= m; ++i) t[i] -= '0';
int u = 0, v = 0;
if(n == m) {
for(int i = 1; i <= n; ++i) {
if(u == v)
for(int j = s[i] + 1; j < t[i]; ++j)
++g[Tra(u, j)][n - i];
else {
for(int j = s[i] + 1; j < 10; ++j)
++g[Tra(u, j)][n - i];
for(int j = 0;j < t[i]; ++j)
++g[Tra(v, j)][n - i];
}
u = Tra(u, s[i]); v = Tra(v, t[i]);
}
++g[u][0]; if(u != v) ++g[v][0];
}
else {
for(int i = 1;i <= n; ++i) {
for(int j = s[i] + 1; j < 10; ++j)
++g[Tra(u, j)][n - i];
u = Tra(u, s[i]);
}
for(int i = 1;i <= m;++i) {
for(int j = 0;j < t[i]; ++j)
++g[Tra(v, j)][m - i];
v = Tra(v, t[i]);
}
++g[u][0]; ++g[v][0];
for(int i = n + 1; i < m; ++i)
for(int j = 1;j < 10; ++j)
++g[Tra(0, j)][i - 1];
}
int L = 1, R = 0;
ch[0][0] = 0;
for(int i = 0;i < 10; ++i)
if(ch[0][i])
q[++R] = ch[0][i];
for(int u = q[L];L <= R; u = q[++L]) {
for(int i = 0;i < 10; ++i) {
int &v = ch[u][i];
if(!v) v = ch[fa[u]][i];
else fa[v] = ch[fa[u]][i], q[++R] = v;
}
for(int i = 0;i <= m; ++i)
g[u][i] += g[fa[u]][i];
}
for(int i = 1;i <= sz; ++i)
for(int j = 1;j <= len; ++j)
g[i][j] += g[i][j - 1];
std::memset(f, -1, sizeof(f));
f[0][0] = 0;
for(int i = 0;i <= len; ++i)
for(int j = 0;j <= sz; ++j)
if(~f[i][j]) {
f[i][j] += g[j][len - i];
for(int k = 0;k < 10; ++k)
Up(f[i + 1][ch[j][k]], f[i][j]);
}
int ans = 0;
for(int i = 0;i <= sz; ++i)
Up(ans, f[len][i]);
for(int i = 0;i <= sz; ++i)
if(ans == f[len][i])
ok[len][i] = 1;
for(int i = len - 1; ~i; --i)
for(int u = 0;u <= sz; ++u)
for(int k = 0;k < 10; ++k)
if(Can(i, u, k))
ok[i][u] |= ok[i + 1][ch[u][k]];
printf("%d\n", ans);
for(int i = 0, u = 0;i < len; ++i)
for(int j = 0;j < 10; ++j)
if(Can(i, u, j) && ok[i + 1][ch[u][j]]) {
putchar(j + '0'); u = ch[u][j]; break;
}
return puts(""), 0;
}