Codeforces Round #852 (Div. 2) 赛后做题
Codeforces Round #852 (Div. 2)
B. Fedya and Array
solution:
观察样例,好像数的构造没什么规律,但答案均为 $2(x-y)$
定义峰指比环上的两边大的数,谷指比环上的两边小的数
思考要让数的个数尽量少,那单调下降/上升的一段无贡献,且每个产生贡献的峰/谷的数值尽量大
我们不妨让第一个从峰 $x$ 开始,先下降到谷 $y$,再上升到 $x-1$,刚好满足了环的要求,$n=2(x-y)$
为什么最优呢?
假设最终序列从峰开始,峰谷交替,依次为 $x_i,y_i$,有 $k$ 对
因为每当从任意一个峰到达一个谷,至少需要 $x_i-y_i$ 个数(不包含最后的谷),同理从谷到下一个峰要 $x_{i+1}-y_i$ 个数,由于在环上,满足差的绝对值等于一的情况下首尾还至少要 $x_1-y_k$ 个数让峰谷连接
而 $\sum_{i=1}^k x_i=x,\sum_{i=1}^k y_i=y$,把上述相加,就是 $2(x-y)$
code:
int t, n, mx, mn, a[200010];
int main()
{
t = read();
while(t--)
{
mx = read(), mn = read(), n = 0;
for(reg int i = mx; i >= mn; --i) a[++n] = i;
for(reg int i = mn + 1; i < mx; ++i) a[++n] = i;
print(n), putchar('\n');
for(reg int i = 1; i <= n; ++i) print(a[i]), putchar(' ');
putchar('\n');
}
return 0;
}
C. Dora and Search
solution:
暴力找肯定不行,但先无脑的想,如果开头结尾没有 $1$ 且没有 $n$,直接输出 $(1,n)$ 即可
那如果有呢?序列最大/小值一定在开头/结尾,不选它剩下数的最值一定不是它,不影响剩下数的答案,递归地思考不选它,把开头或结尾向里挪一格
问题变为了一个规模为 $n-1$ 的子问题!
这样下去,如果左端点与右端点相交还没找到答案则无解,若有解直接输出
code:
int main()
{
t = read();
while(t--)
{
n = read(), st = mn = 1, ed = mx = n, flag = 0;
for(reg int i = 1; i <= n; ++i) a[i] = read();
while(st < ed)
{
if(a[st] != mn && a[st] != mx && a[ed] != mn && a[ed] != mx)
{
print(st), putchar(' '), print(ed), putchar('\n'), flag = 1;
break;
}
if(a[st] == mn) ++st, ++mn;
else if(a[st] == mx) ++st, --mx;
if(a[ed] == mn) --ed, ++mn;
else if(a[ed] == mx) --ed, --mx;
}
if(!flag) putchar('-'), putchar('1'), putchar('\n');
}
return 0;
}
D. Moscow Gorillas
solution:
$MEX([l,r])$ 真的很不好维护,何况还有两个排列
但从简单的问题想起,如果 $MEX([l,r])=1$,则 $[l,r]$ 一定不包含 $1$,可以直接算出方案数!
那么,枚举 $MEX$ 的值为 $x$,设 $p_{idp}=x,q_{idq}=x$,则 $[l,r]$ 中一定包含 $1\sim x-1$ 且不包含 $x$
可以在从小到大枚举时维护恰好包含 $1\sim x-1$ 的区间,记它为 $[l,r]$
分类讨论端点的大小:
-
$l\le idp,idq\le r$,则不可能有解
-
$idp,idq\le l$,$ans=(l-\max\{idp,idq\})\cdot(n-r+1)$
-
$idp,idq\ge r$,$ans=(\min\{idp,idq\}-r)\cdot l$
-
else, $ans=(l-min\{idp,idq\})\cdot (r-\max\{idp,idq\})$
每次更新 $l,r$,记得初始化 $x=1$ 时和加上全选的情况
code:
inline ll c2(ll x) {return x * (x + 1) / 2;}
int main()
{
n = read();
for(reg ll i = 1; i <= n; ++i) p[i] = read(), idp[p[i]] = i;
for(reg ll i = 1; i <= n; ++i) q[i] = read(), idq[q[i]] = i;
l = min(idp[1], idq[1]), r = max(idp[1], idq[1]), ans += c2(l - 1) + c2(n - r) + c2(r - l - 1);
for(reg ll i = 2; i <= n; ++i)
{
lasl = l, lasr = r;
if((idp[i] >= l && idp[i] <= r) || (idq[i] >= l && idq[i] <= r))
{
l = min({l, idp[i], idq[i]}), r = max({r, idp[i], idq[i]});
continue;
}
l = min({l, idp[i], idq[i]}), r = max({r, idp[i], idq[i]});
if(max(idp[i], idq[i]) < lasl) ans += (lasl - max(idp[i], idq[i])) * (n - lasr + 1);
else if(min(idp[i], idq[i]) > lasr) ans += lasl * (min(idp[i], idq[i]) - lasr);
else ans += (lasl - l) * (r - lasr);
}
printf("%lld", ans + 1);
return 0;
}
E. Velepin and Marketing
solution:
dp好题,但不擅长猜结论的我推不出推论啊……
首先将 $a_i$ 从小到大排序
那让尽可能多的人满足
引理1:满足的人一定是序列的一个前缀
还比较好证,如果不是一个前缀,则换成前面 $a_i$ 更小的人一定还满足,答案不劣
但如果对于每本书,阅读它的人是两段不交的区间,就不好处理,所以神奇的地方在这里:
引理2:阅读每本书的人一定是连续的一段区间
受评论区大佬的启发,这里考虑交换证明
先设有两段不交区间 $A,B$,那想让 $A$ 中元素与在 $A$ 右边元素交换,实现 $A$ 右移,注意每段区间长度不变
看 $A$,右边元素 $a$ 更大都能满足,$A$ 中交换过去的一定满足
看右边元素,$B$ 中元素 $a$ 比它大都能满足,右边交换到 $A$ 的一定能满足
所以,答案不劣,$A$ 可以一直到与 $B$ 相邻位置,多个区间同理
根据引理 1,巧妙转化定 $k$ 求最大前缀长度为定前缀长度求最大 $k$
我们想到设 $f_i$ 表示处理了 $i$ 前元素后,前 $i$ 个元素最多被分成的集合数
$sum_i$ 为恰好划分为 $i$ 个集合时满足的最大人数
转移:$f_i=\max_{j\le i-a_i} f_j+1,sum_i=\max\{n-i+f_i\}$,用前缀最大值优化
但若 $a_i>i$,此时不可能满足,$f_i=-\infty,sum_i=\max\{n-a_i+1\}$
$sum_i$ 求后缀最大值,得到至少划分为 $i$ 个集合时满足的最大人数,直接输出 $sum_{k_i}$
code:
int main()
{
n = read();
for(reg int i = 1; i <= n; ++i) a[i] = read();
sort(a + 1, a + n + 1);
for(reg int i = 1; i <= n; ++i)
{
if(a[i] > i) // 不可能满足,最多 a[i] 个人一个集合,剩下一人一个集合
f[i] = -inf, sum[n - a[i] + 1] = max(sum[n - a[i] + 1], i);
else // 其实是这段连续段的长度 >= a[i],找最大的 f[j],用上前缀最大值
f[i] = f[i - a[i]] + 1, sum[n - i + f[i]] = max(sum[n - i + f[i]], i);
f[i] = max(f[i], f[i - 1]); // 前缀最大值优化
}
for(reg int i = n - 1; i >= 0; --i) sum[i] = max(sum[i + 1], sum[i]);
q = read();
for(reg int i = 1; i <= q; ++i)
{
k = read();
print(sum[k]), putchar('\n');
}
return 0;
}