A. Showstopper
【题意】
给两个长度为 n n n 的序列 a , b a,b a,b,每次操作可以选择一个位置 i i i 交换 a i , b i a_i,b_i ai,bi。问能否通过任意次操作使得 a n , b n a_n,b_n an,bn 分别是 a , b a,b a,b 中最大的元素。
【分析】
const int N = 5e5 + 7;
int a[N], b[N];
int n;
void SolveTest() {
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
for (int i = 1; i <= n; i++) {
scanf("%lld", &b[i]);
}
if (a[n] < b[n]) {
swap(a[n], b[n]);
}
for (int i = 1; i < n; i++) {
if (max(a[i], b[i]) > a[n] or min(a[i], b[i]) > b[n]) {
puts("No");
return;
}
}
puts("Yes");
}
B. Three Sevens
【题意】
给 m m m 个集合。需要从每个集合中找到一个元素,满足它在之后的集合中再也没有出现过,或报告不可能。
【分析】
int m, n;
set<int> st[50005];
int ans[50005];
void SolveTest() {
cin >> m;
for (int i = 1; i <= m; i++) {
st[i].clear();
cin >> n;
for (int j = 1; j <= n; j++) {
int x;
cin >> x;
st[i].insert(x);
}
}
set<int> tot;
for (int i = m; i >= 1; i--) {
bool flg = 0;
for (int x : st[i]) {
if (tot.find(x) == tot.end()) {
flg = 1;
ans[i] = x;
}
tot.insert(x);
}
if (!flg) {
puts("-1");
return;
}
}
for (int i = 1; i <= m; i++) {
printf("%lld ", ans[i]);
}
puts("");
}
C. Candy Store
【题意】
有 n n n 种糖果,每种糖果有个数 a [ i ] a[i] a[i],单价 b [ i ] b[i] b[i]。现在需要将每一种糖果,分成若干数量相同的组,第 i i i 种糖果的每一组有 d [ i ] d[i] d[i] 个。第 i i i 种糖果的价格标签为 c [ i ] = b [ i ] × d [ i ] c[i] = b[i]\times d[i] c[i]=b[i]×d[i]。如果两个相邻的糖果价格标签相同,则他们可以共用一个价格标签。
现在给出所有 a a a 和 b b b ,需要设计每种糖果的 d d d,使得总的价格标签数量最少。
题目限定糖果顺序不能打乱。
【分析】
连续若干个糖果 [ l , r ] [l,r] [l,r] 可以合并为一个价格标签 x x x 。
首先每一组的数量
d
[
i
]
=
x
b
[
i
]
d[i] = \dfrac{x}{b[i]}
d[i]=b[i]x 都是整数,所以对于
i
∈
[
l
,
r
]
i\in[l,r]
i∈[l,r],都有
b
[
i
]
∣
x
b[i]\mid x
b[i]∣x,即
lcm
(
b
l
,
.
.
.
b
r
)
∣
x
\operatorname{lcm}(b_l,...b_r) \mid x
lcm(bl,...br)∣x
其次组的个数一定是整数,即
d
[
i
]
∣
a
[
i
]
d[i] \mid a[i]
d[i]∣a[i]。这个不好直接处理,但是注意到组的个数其实就是
a
[
i
]
×
b
[
i
]
x
\dfrac{a[i]\times b[i]}{x}
xa[i]×b[i],即该种物品的总价除以每一组的总价。令
t
[
i
]
=
a
[
i
]
×
b
[
i
]
t[i] = a[i]\times b[i]
t[i]=a[i]×b[i],则对于
i
∈
[
l
,
r
]
i\in [l,r]
i∈[l,r],都有
x
∣
t
[
i
]
x\mid t[i]
x∣t[i],即
x
∣
gcd
(
t
l
,
.
.
.
,
t
r
)
x\mid \gcd(t_l, ...,t_r)
x∣gcd(tl,...,tr)
综上可以得到
lcm
(
b
l
,
.
.
,
b
r
)
∣
gcd
(
t
l
,
.
.
.
,
t
r
)
\operatorname{lcm}(b_l,..,b_r) \mid \gcd(t_l, ...,t_r)
lcm(bl,..,br)∣gcd(tl,...,tr)
从左到右扫描即可。
const int N = 5e5 + 7;
int n;
int a[N], b[N], t[N];
int gcd(int x, int y) {
return __gcd(x, y);
}
int lcm(int x, int y) {
return x / gcd(x, y) * y;
}
void SolveTest() {
cin >> n;
int res = 0, g = 0, l = 1;
for (int i = 1; i <= n; i++) {
scanf("%lld%lld", &a[i], &b[i]);
t[i] = a[i] * b[i];
g = gcd(g, t[i]);
l = lcm(l, b[i]);
if (i == 1 or g % l) {
res++;
g = t[i];
l = b[i];
}
}
printf("%lld\n", res);
}
D. Shocking Arrangement
【题意】
给定序列
a
a
a,保证
a
a
a 中的元素和为
0
0
0。需要将
a
a
a 重新排序,满足
max
1
≤
l
≤
r
≤
n
∣
∑
i
=
l
r
a
i
∣
<
max
i
=
1
n
a
i
−
min
i
=
1
n
a
i
\max_{1\le l\le r\le n}|\sum_{i=l}^{r}a_i|<\max_{i=1}^{n}a_i - \min_{i=1}^{n}a_i
1≤l≤r≤nmax∣i=l∑rai∣<i=1maxnai−i=1minnai
也就是说:任何一个区间的和的绝对值都小于序列最大值与最小值的差。
输出一组可行的排序,或者输出不可能。
【分析】
比较直观的想法是要尽可能地把正数和负数交替开,这样可以避免出现一段连续区间都是同号,加起来绝对值过大的情况。
比赛的时候我尝试过一种错误方法,以失败告终:把正数和负数分别放到两个序列中,并排序。正数从大到小,负数从小到大。对于答案序列 a n s ans ans,把连续的一段符号相同的区间成为一个段。贪心地让这个段的和的绝对值小于 max i = 1 n a i − min i = 1 n a i \max_{i=1}^{n}a_i - \min_{i=1}^{n}a_i maxi=1nai−mini=1nai (定值)。如果继续添加同号元素会让这个段的和绝对值超标,那么就转为添加另一个符号的元素,即开始一个新的段。
这个做法失败的原因在于,这种贪心方法虽然可以保证当前段的和绝对值不超标,但是可能导致更多另一种符号的元素堆积在后面。例如 3 , 3 , − 1 , − 1 , − 1 , − 1 3,3,-1,-1,-1,-1 3,3,−1,−1,−1,−1。一个想法是,既然该例中负数更多,那么就在答案中先从负数开始加。但是贪心的时候无法保证正负数个数的平衡,无法杜绝分段个数增加之后某种符号的元素在末尾堆积。
下面开始介绍正解:
一个关键的点是利用起来元素和为 0 0 0 这个条件。这个条件能推出的结论是:序列无解的唯一情况是 a a a 中所有元素均为 0 0 0,否则一定能构造出来合法解。
合法解的构造方法如下。
- 记构造的答案为 a n s ans ans。首先把所有的 0 0 0 放到 a n s ans ans 的最前面。这样 a a a 中剩余的元素都不为 0 0 0;
- 记
a
n
s
ans
ans 已有的元素和为
s
u
m
sum
sum。重复以下操作直到
a
a
a 为空。
- 如果 s u m ≤ 0 sum\le 0 sum≤0:从 a a a 中剩余的元素中任选一个正数加入 a n s ans ans 末尾,并把它从 a a a 中删除;
- 如果 s u m > 0 sum>0 sum>0:从 a a a 中剩余的元素中任选一个负数加入 a n s ans ans 末尾,并把它从 a a a 中删除。
下面证明这个构造方法的正确性。
记
a
n
s
ans
ans 的前缀和为
P
[
i
]
P[i]
P[i]。则可转化所求为
max
1
≤
l
≤
r
≤
n
∣
∑
i
=
l
r
a
n
s
i
∣
=
max
i
=
1
n
P
(
i
)
−
min
i
=
1
n
P
(
i
)
\max_{1\le l\le r\le n}|\sum_{i=l}^{r}ans_i|=\max_{i=1}^{n}P(i) - \min_{i = 1}^{n}P(i)
1≤l≤r≤nmax∣i=l∑ransi∣=i=1maxnP(i)−i=1minnP(i)
max
i
=
1
n
P
(
i
)
\max_{i=1}^{n}P(i)
maxi=1nP(i) 一定是在
i
i
i 为正数的时候取到,记下标为
u
u
u。由于在添加正数
a
n
s
[
u
]
ans[u]
ans[u] 的时候保证了
P
[
u
−
1
]
≤
0
P[u-1] \le 0
P[u−1]≤0 ,所以有
P
[
u
]
=
P
[
u
−
1
]
+
a
n
s
[
u
]
≤
a
n
s
[
u
]
P[u]=P[u - 1] + ans[u] \le ans[u]
P[u]=P[u−1]+ans[u]≤ans[u]。
min i = 1 n P ( i ) \min_{i=1}^{n}P(i) mini=1nP(i) 一定是在 i i i 为负数的时候取到,记下标为 v v v。由于在添加负数 a n s [ v ] ans[v] ans[v] 的时候保证了 P [ v − 1 ] > 0 P[v-1] > 0 P[v−1]>0 ,所以有 P [ v ] = P [ v − 1 ] + a n s [ v ] > a n s [ v ] P[v]=P[v - 1] + ans[v] > ans[v] P[v]=P[v−1]+ans[v]>ans[v]。
因此可以得到
max
1
≤
l
≤
r
≤
n
∣
∑
i
=
l
r
a
n
s
i
∣
=
max
i
=
1
n
P
(
i
)
−
min
i
=
1
n
P
(
i
)
≥
P
(
u
)
−
P
(
v
)
>
a
n
s
u
−
a
n
s
v
=
max
i
=
1
n
a
n
s
i
−
min
i
=
1
n
a
n
s
i
\begin{aligned} \max_{1\le l\le r\le n}|\sum_{i=l}^{r}ans_i| &= \max_{i=1}^{n}P(i) - \min_{i = 1}^{n}P(i)\\ &\ge P(u)-P(v)\\ &> ans_u-ans_v \\ &= \max_{i=1}^{n}ans_i - \min_{i = 1}^{n}ans_i \end{aligned}
1≤l≤r≤nmax∣i=l∑ransi∣=i=1maxnP(i)−i=1minnP(i)≥P(u)−P(v)>ansu−ansv=i=1maxnansi−i=1minnansi
int n;
void SolveTest() {
int cnt0 = 0, sum = 0, x;
queue<int> q1, q2;
vector<int> ans;
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%lld", &x);
if (x > 0) {
q1.push(x);
} else if (x < 0) {
q2.push(x);
} else {
cnt0++;
}
}
if (cnt0 == n) {
puts("No");
return;
}
while (cnt0) {
ans.push_back(0);
cnt0--;
}
while (q1.size() or q2.size()) {
if (sum <= 0 and q1.size()) {
int x = q1.front();
sum += x;
ans.push_back(x);
q1.pop();
} else {
int x = q2.front();
sum += x;
ans.push_back(x);
q2.pop();
}
}
puts("Yes");
for (int x : ans) {
printf("%lld ", x);
}
puts("");
}
E. Multitest Generator
【题意】
定义一个数组 b [ 1 , m ] b[1,m] b[1,m] 是一个 test:满足 b 1 = m − 1 b_1=m-1 b1=m−1。
定义一个序列 b [ 1 , m ] b[1,m] b[1,m] 是一个 multitest:满足 b [ 2 , m ] b_[2,m] b[2,m] 可以被划分为 b 1 b_1 b1 个子序列,每一个子序列都是一个 test。
定义函数 f ( b [ 1 , m ] ) f(b[1,m]) f(b[1,m]):对于序列 b [ 1 , m ] b[1,m] b[1,m],每次操作可以将一个元素替换为任意数字,至少要多少次操作可以使它变为一个 multitest。
给定序列 a a a,对于所有的 i ∈ [ 1 , n − 1 ] i\in[1,n-1] i∈[1,n−1],求 f ( a [ i , n ] ) f(a[i,n]) f(a[i,n])。
n , a [ i ] ∈ 300000 n,a[i]\in 300000 n,a[i]∈300000
【分析】
对于任何一个长度为 n n n 的序列,一定可以通过不超过两次操作使得它是一个 multitest。即让第一个数字为 1 1 1,第二个数字为 n − 2 n-2 n−2。
首先讨论什么情况下需要 0 0 0 次操作。
如果 a [ i , n ] a[i,n] a[i,n] 是由若干个连续的 test 组成,那么定义下标 i i i 是 ”好下标”。
对于每一个下标,我们想要知道它是否为 “好下标”。如果是的话,这个后缀由多少个 tests 组成。
定义 n x t [ i ] = i + a i + 1 nxt[i] = i + a_i + 1 nxt[i]=i+ai+1。这个定义的想法是如果 i i i 是一个 test 的首元素,那么 i + a i + 1 i+a_i +1 i+ai+1 就是下一个 test 的首元素。
那么下标 i i i 是 “好下标” 等价于:链 i → n x t [ i ] → n x t [ n x t [ i ] ] → . . . i \rightarrow nxt[i] \rightarrow nxt[nxt[i]] \rightarrow ... i→nxt[i]→nxt[nxt[i]]→... 最终会在 n + 1 n+1 n+1 结束。
可以简单地用 DP 处理每个这个链是否会在 n + 1 n+1 n+1 结束,以及这条链有多少个 test,即链的深度 d e p [ i ] dep[i] dep[i]。
nxt[i] = i + a[i] + 1;
if (nxt[i] == n + 1 || (nxt[i] <= n && good[nxt[i]])) {
good[i] = 1;
} else {
good[i] = 0;
}
dep[i] = 1 + dep[min(nxt[i], n + 1)];
综上,为了确定后缀 a [ i , n ] a[i,n] a[i,n] 是否为一个 multitest,我们需要判断 i + 1 i+1 i+1 是否为 “好下标”,并查询后缀 a [ i + 1 , n ] a[i+1, n] a[i+1,n] 是否包含 a i a_i ai 个test。
然后讨论什么情况下需要 1 1 1 次操作。
如果需要一次操作的话,要么是第一个元素(表示有多少个 test)改变了,要么是某个 test 当中的元素改变了。
第一种情况,第一个元素改变了。当且仅当 i + 1 i+1 i+1 是 “好下标”。
第二种情况,某个 test 当中的某个元素改变了。
改变后的下标将会是某个 test 的首元素,即 i + 1 , n x t [ i + 1 ] , n x t [ n x t [ i + 1 ] ] . . . i+1,nxt[i+1],nxt[nxt[i+1]]... i+1,nxt[i+1],nxt[nxt[i+1]]... 之一。否则这条链会保持原状,什么也没有改变。那么这个改变后的下标表示了。
我们考虑后缀 a [ i , n ] a[i,n] a[i,n] 通过改变某一个元素,最多能使这个后缀包含多少个连续的 test,记为 d [ i ] d[i] d[i]。为什么要考虑最多呢?因为如果能得到 d [ i ] d[i] d[i] 个 test,那么对于所有 c n t < d [ i ] cnt<d[i] cnt<d[i],都一定能得到 c n t cnt cnt 个 test (只需要调整改变的那个元素的大小,让若干个 test 合并为一个 test 即可)。
所以如果 d [ i + 1 ] ≥ a i d[i+1] \ge a_i d[i+1]≥ai,那么后缀 a [ i , n ] a[i,n] a[i,n] 可以通过一次操作变为 multitest。
下面考虑怎样维护 d d d 的值。从后向前遍历的时候,维护 m a x d e p [ i ] maxdep[i] maxdep[i],即:从 i i i 以及它之后出发的链最大深度。
if (good[i]) {
maxdep[i] = max(maxdep[i + 1], dep[i]);
} else {
maxdep[i] = maxdep[i + 1];
}
那么我们可以用一次操作修改 a [ i ] a[i] a[i],使得以 a [ i ] a[i] a[i] 打头的 test 能够接上它之后深度最大的那一条链。
d[i] = 1 + maxdep[i + 1];
完整代码如下:
const int N = 5e5 + 7;
int n;
bool good[N];
int a[N], nxt[N], dep[N], maxdep[N], d[N], ans[N];
void SolveTest() {
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
dep[n + 1] = 0;
maxdep[n + 1] = 0;
d[n + 1] = 0;
for (int i = n; i >= 1; i--) {
nxt[i] = i + a[i] + 1;
if (nxt[i] == n + 1 || (nxt[i] <= n and good[nxt[i]])) {
good[i] = 1;
} else {
good[i] = 0;
}
dep[i] = 1 + dep[min(nxt[i], n + 1)];
d[i] = 1 + maxdep[i + 1];
if (nxt[i] <= n + 1) {
d[i] = max(d[i], 1 + d[nxt[i]]);
}
if (good[i]) {
maxdep[i] = max(maxdep[i + 1], dep[i]);
} else {
maxdep[i] = maxdep[i + 1];
}
}
for (int i = 1; i < n; i++) {
if (good[i + 1] && dep[i + 1] == a[i]) {
ans[i] = 0;
} else if (good[i + 1] || d[i + 1] >= a[i]) {
ans[i] = 1;
} else {
ans[i] = 2;
}
}
for (int i = 1; i < n; i++) {
printf("%lld ", ans[i]);
}
puts("");
}