A. Bestie
题意:
给一个长度为
n
n
n 的整数序列
S
S
S,并定义一种操作:
选择一个下标
i
(
1
≤
i
≤
n
)
i(1\leq i\leq n)
i(1≤i≤n),使
S
[
i
]
=
g
c
d
(
S
[
i
]
,
i
)
S[i]=gcd(S[i],i)
S[i]=gcd(S[i],i),代价为
n
−
i
+
1
n-i+1
n−i+1。
问花费最小的代价,使得序列所有数的
g
c
d
gcd
gcd 值为
1
1
1。
思路:
首先有一个比较有用的性质,即相邻两个数互质,也就是说他们的
g
c
d
gcd
gcd 值一定为
1
1
1,那么意味着操作最多被执行两次。
这样题目就很清晰了,枚举花费为
0
,
1
,
2
,
3
(
1
+
2
)
0,1,2,3(1+2)
0,1,2,3(1+2) 的情况即可。
时间复杂度: O ( n ) O(n) O(n)
int n;
int s[N], d[N];
signed main() {
IOS
cf{
sf(n);
for (int i = 1; i <= n; i++)
sf(s[i]);
int e = s[1];
for (int i = 2; i <= n; i++)
e = __gcd(e, s[i]);
if (e == 1) {
pfn(0);
continue;
}
int a = __gcd(s[n], n);
for (int i = 1; i < n; i++)
a = __gcd(a, s[i]);
if (a == 1) {
pfn(1);
continue;
}
if (n > 1) {
a = __gcd(s[n - 1], n - 1);
for (int i = 1; i < n - 1; i++)
a = __gcd(a, s[i]);
a = __gcd(a, s[n]);
if (a == 1) {
pfn(2);
continue;
}
a = __gcd(s[n - 1], n - 1);
a = __gcd(a, __gcd(s[n], n));
for (int i = 1; i < n - 1; i++)
a = __gcd(a, s[i]);
if (a == 1) {
pfn(3);
continue;
}
}
}
return 0;
}
B. Ugu
题意:
给一个长度为
n
n
n 的
01
01
01 字符串
S
S
S,并定义一种操作:
选择一个下标
i
(
1
≤
i
≤
n
)
i(1\leq i\leq n)
i(1≤i≤n),并使所有
S
[
j
]
(
i
≤
j
&
j
≤
n
)
S[j](i\leq j \& j\leq n)
S[j](i≤j&j≤n) 反转,也就是
0
−
>
1
、
1
−
>
0
0->1、1->0
0−>1、1−>0。
问最少操作次数使
S
S
S 变成非严格意义递增字符串。
思路:
首先先排除初始符合条件的情况。
然后观察样例,会发现最小操作次数和
01
/
10
01/10
01/10 的转折点个数有关,这个从结果倒推原因很简单,就不过多赘述了。
要注意当所有转折点均反转的话,最终字符串的所有字符会全部相同,而
0
…
…
01
…
…
1
0……01……1
0……01……1 的情况同样是合法的,特判下即可。
时间复杂度: O ( n ) O(n) O(n)
int n;
char s[N];
signed main() {
IOS
cf{
sf(n);
sf(s + 1);
bool st;
if (s[1] == '0')
st = false;
else
st = true;
int cnt = 0;
for (int i = 2; i <= n; i++)
if (s[i] != s[i - 1])
cnt++;
if (cnt == 0 || (st == false && cnt == 1)) {
pfn(0);
continue;
}
if (st)
pfn(cnt);
else
pfn(cnt - 1);
}
return 0;
}
C1. Sheikh (Easy version)
题意:
给一个长度为
n
n
n 的非负整数序列
S
S
S,并定义一种函数:
f
(
l
,
r
)
=
(
S
[
l
]
+
S
[
l
+
1
]
+
…
…
+
S
[
r
]
)
−
(
S
[
l
]
⊕
S
[
l
+
1
]
⊕
…
…
⊕
S
[
r
]
)
f(l,r)=(S[l]+S[l+1]+……+S[r])-(S[l]\oplus S[l+1]\oplus……\oplus S[r])
f(l,r)=(S[l]+S[l+1]+……+S[r])−(S[l]⊕S[l+1]⊕……⊕S[r])
然后给出一个区间
[
L
,
R
]
[L,R]
[L,R] ,问找到一子区间
[
l
,
r
]
(
L
≤
l
≤
r
≤
R
)
[l,r](L\leq l\leq r\leq R)
[l,r](L≤l≤r≤R) ,使其在
f
(
l
,
r
)
f(l,r)
f(l,r) 值最大的情况下,
r
−
l
+
1
r-l+1
r−l+1 的值最小。
思路:
确定一点,由于异或和加法的特点, m a x f ( l , r ) max{f(l,r)} maxf(l,r) 一定等于 f ( L , R ) f(L,R) f(L,R) ,那直接双指针扫一遍即可。
时间复杂度: O ( n ) O(n) O(n)
int n, m;
int s[N], d[N], e[N];
int L, R;
int get(int l, int r) {
return d[r] - d[l - 1] - (e[r] ^ e[l - 1]);
}
signed main() {
IOS
cf{
sf2(n, m);
for (int i = 1; i <= n; i++)
sf(s[i]), d[i] = d[i - 1] + s[i], e[i] = e[i - 1] ^ s[i];
sf2(L, R);
int ma = get(L, R);
int a = L, b = R;
for (int i = L, r = L; i <= R; i++) {
while (r < i)
r++;
while (r <= R && get(i, r) != ma)
r++;
if (r > R)
break;
if (r - i < b - a) {
b = r;
a = i;
}
}
pp(a, b);
}
return 0;
}
C2. Sheikh (Hard Version)
题意:
题意与 C 1 C1 C1 相同,只不过询问的区间由 1 1 1 个变为 n n n 个。
思路:
首先,序列中值为 0 0 0 的下标是完全没有用处的。所以我们可以重载下标,将所有值为 0 0 0 的下标省去,用数组模拟或链表均可,注意每个下标应当映射为的左右边界是不同的。
由于数组中的所有元素的值均大于零,那么接下来就是本题最重要的一个性质,即
f
(
L
,
R
)
>
f
(
L
+
30
,
R
)
f(L,R)>f(L+30,R)
f(L,R)>f(L+30,R) 或者这样表示
f
(
L
,
R
)
>
f
(
L
,
R
−
30
)
f(L,R)>f(L,R-30)
f(L,R)>f(L,R−30)(假使下标均合法)。
因为
1
<
S
[
]
≤
1
0
9
1<S[]\leq 10^9
1<S[]≤109,即转化为二进制最大有
30
30
30 位,那么由于鸽笼原理,
31
31
31 个正数中一定有两个数,它们的二进制上有一位均为
1
1
1 ,也就是说,只要区间长度相差
31
31
31 以上,
f
(
,
)
f(,)
f(,) 的值必然会变化。
那么思路就很明确了,枚举所有可能是解的
31
∗
31
31*31
31∗31 对下标即可。
尤其要注意,由于当前使用的下标均为重载之后的新下标,在比较区间长度时必须还原到初始下标进行计算。
时间复杂度: O ( n ∗ 3 1 2 ) O(n*31^2) O(n∗312)
int n, m;
int s[N], d[N], e[N];
int L, R;
int idl[N], idr[N];
int re[N];
int get(int l, int r) {
return d[r] - d[l - 1] - (e[r] ^ e[l - 1]);
}
signed main() {
IOS
cf{
sf2(n, m);
int len = 0;
for (int i = 1, x; i <= n; i++) {
sf(x);
if (x > 0) {
s[++len] = x;
d[len] = d[len - 1] + s[len];
e[len] = e[len - 1] ^ s[len];
idl[i] = idr[i] = len;
re[len] = i;
} else {
idl[i] = len + 1;
idr[i] = len;
}
}
while (m--) {
sf2(L, R);
int l = idl[L], r = idr[R];
if (l > r || l == n + 1) {
pp(L, L);
continue;
}
int ma = get(l, r);
if (ma == 0) {
pp(L, L);
continue;
}
int a = l, b = r;
for (int i = 0 + l; i <= 30 + l && i <= len; i++)
for (int j = r - 0; j >= r - 30 && j >= 1; j--)
if (i <= j && get(i, j) == ma && re[j] - re[i] < re[b] - re[a]) {
b = j;
a = i;
}
pp(re[a], re[b]);
}
}
return 0;
}
D1. Balance (Easy version)
题意:
需要实现一个数据结构,至此以下操作:
- + x +\ x + x:添加 x x x 到数据结构中,保证 x x x 没有出现过。
- ? k ?\ k ? k:查询最小未出现的 k k k 的倍数。
思路:
用离散化数据结构保存一个数是否出现过,这一步可以由
s
e
t
set
set 或
m
a
p
map
map 来实现;
由调和级数可知,如果暴力查找的话,最多进行
∑
i
=
1
n
n
i
≤
n
∗
log
n
\sum\limits_{i=1}^{n}{\frac{n}{i}}\leq n*\log{n}
i=1∑nin≤n∗logn 次加法。
因此直接暴力即可。
时间复杂度: O ( n ∗ log n ) O(n*\log{n}) O(n∗logn)
char op[2];
int x;
map<int, int> d;
set<int> s;
signed main() {
IOS
cf{
sf2(op, x);
if (op[0] == '+') {
s.insert(x);
} else {
if (d.count(x) == 0)
d[n] = x;
while (s.find(d[x]) != s.end())
d[x] += x;
pfn(d[x]);
}
}
return 0;
}
D2. Balance (Hard version)
题意:
与 D 1 D1 D1 类似,只不过添加了一种操作:
- − x -\ x − x:删除 x x x 从数据结构中,保证 x x x 之前出现过。
思路:
这道题是看佬的题解补的,所以解释可能不是太准确。
因为有些曾经存在过的数
X
X
X 被删除,使得一些出现过的
X
X
X 的因数
y
y
y 的
y
−
m
e
x
y-mex
y−mex 的值会变化,为了处理这种情况,就需要用到计算普通的
m
e
x
mex
mex 值用到的方法,即将未出现的符合条件的值放入 set<int> del
中,那么只要它不空,那么对应的 *del[y].begin()
就是所求的
y
−
m
e
x
y-mex
y−mex 的值。
那么怎么去更新 del[y]
中的值呢?我们需要保存,哪些数使
y
−
m
e
x
y-mex
y−mex 的值发生改变,这时我们发现这些数就是
X
X
X ,因此我们只需要在求
y
−
m
e
x
y-mex
y−mex 的值的时候,将
y
y
y 放入那些存在的、影响
y
−
m
e
x
y-mex
y−mex 的数,也就是
X
X
X ,的 vector<int> change
中,那么当
X
X
X 被删除的时候,那些
d
e
l
[
y
]
del[y]
del[y] 就需要更新,这时就可以从 change[X]
中去找
y
y
y 。
更详细的 s t l stl stl 容器解释在代码注释中。
时间复杂度: O (不好说) O(不好说) O(不好说)
int x;
char op[2];
map<int, int>vis;
//当前是否存在X
map<int, int>last;
//层算过的最大的X-mex
map<int, set<int>>del;
//出现过,但是当前不存在的X的倍数
map<int, vector<int>>change;
//存放X的出现过的因数,这些因数的答案曾因为X而改变
signed main() {
IOS
cf{
sf2(op, x);
if (op[0] == '+') {
vis[x] = 1;
for (int y : change[x]) {
del[y].erase(x);
}
} else if (op[0] == '-') {
vis[x] = 0;
for (int y : change[x]) {
del[y].insert(x);
}
} else {
if (!last.count(x))
last[x] = x;
if (del[x].size()) {
cout << *del[x].begin() << endl;
} else {
while (vis[last[x]]) {
change[last[x]].push_back(x);
last[x] += x;
}
cout << last[x] << endl;
}
}
}
return 0;
}