Educational Codeforces Round 126 (Rated for Div. 2)
这场 vp 爆了,属于是被干碎了(怎么回归文化课两周就退步那么多)。
CF1661A Array Balancing
每个位置与下一个位置之间产生的贡献完全独立,因此对于每个位置讨论最小贡献就好了。
CF1661B Getting Zero
模数是 \(2\) 的幂次,因此答案最多在 \(\log\) 级别,因此最多使用少量 \(1\) 操作,一旦开始 \(2\) 操作后进行一操作必然不优,枚举 \(1\) 操作次数即可。
CF1661C Water the Trees
我讨厌分讨。 /dk
显然最后高度不会超过 \(\max h + 1\),因此讨论最高高度是 \(\max h\) 和 \(\max h + 1\) 的两种情况哪种优秀。
因此确定最高高度后可以知道至少需要多少次加 \(1\) 操作和在此情况下要用多少加 \(2\) 操作。
显然我们不能将一个加 \(2\) 拆为两个加 \(1\) ,但可以将两个 \(1\) 变为 \(2\) ,讨论一下才能使两种操作平均即使用天数最少。
恶恶心心。
read(n);
int mx = 0;
for (int i = 1; i <= n; ++i) read(h[i]) , mx = max(mx , h[i]);
LL ans = 1e18;
LL od = 0 , ev = 0;
for (int i = 1; i <= n; ++i) {
if((mx - h[i]) & 1) od ++;
ev += (mx - h[i]) / 2;
}
if(od > ev) {
ans = min(ans , od * 2 - 1);
}
else {
LL k = ev - od;
ev -= k / 3;
ans = min(ans , ev * 2 - (k % 3 == 2));
}
mx ++;
od = 0 , ev = 0;
for (int i = 1; i <= n; ++i) {
if((mx - h[i]) & 1) od ++;
ev += (mx - h[i]) / 2;
}
if(od > ev) {
ans = min(ans , od * 2 - 1);
}
else {
LL k = ev - od;
ev -= k / 3;
ans = min(ans , ev * 2 - (k % 3 == 2));
}
write(ans);
CF1661D Progressions Covering
从后往前考虑,注意到最后一个元素只在一种情况下能增加,则一直加直到满足条件,然后这个操作就没有使用的必要了,因为一定不优。
因此归约成 \(n - 1\) 的情况,树状数组模拟一下就好了。
CF1661E Narrow Components
仔细分析,发现从中间截取一段,最多只有断口的联通块受到影响,讨论一下是否会裂成两个连通块。
首先通过减去完全不在区间内的连通块,我们可以知道答案至少为多少。
然后检查断口,如果为 101
且两个 1
属于同一个连通块则检查在区间内能否被联通,具体地,找到他之后的第一个不是 101
的列,如果该列存在 0
则必然这个连通块被裂开了。
特别地,特判区间内全是 101
的情况。
const int MAXN = 5e5 + 5;
int n;
char s[3][MAXN];
int id[3][MAXN] , cnt , nxt[MAXN] , las[MAXN];
int sl[MAXN] , sr[MAXN];
int R , L;
void dfs(int x , int y) {
if(id[x][y] || s[x][y] == '0') return;
id[x][y] = cnt;
R = max(R , y) , L = min(L , y);
if(x > 0) dfs(x - 1 , y);
if(x < 2) dfs(x + 1 , y);
if(y > 1) dfs(x , y - 1);
if(y < n) dfs(x , y + 1);
}
bool check(int x) {
return (s[0][x] == '0') || (s[1][x] == '0') || (s[2][x] == '0');
}
int main() {
read(n);
for (int i = 0; i < 3; ++i) scanf("%s" , s[i] + 1);
for (int i = 0; i < 3; ++i) for (int j = 1; j <= n; ++j) if(s[i][j] == '1' && !id[i][j]) {
++cnt;R = 0 , L = n + 1;
dfs(i , j);
sl[R] ++ , sr[L] ++;
}
for (int j = 1; j <= n; ++j) {
if(s[0][j] == '1' && s[1][j] == '0' && s[2][j] == '1') las[j] = las[j - 1];
else las[j] = j;
sl[j] += sl[j - 1];
}
nxt[n + 1] = n + 1;
for (int j = n; j >= 1; --j) {
if(s[0][j] == '1' && s[1][j] == '0' && s[2][j] == '1') nxt[j] = nxt[j + 1];
else nxt[j] = j;
sr[j] += sr[j + 1];
}
int Q;read(Q);
while(Q -- > 0) {
int l , r;
read(l),read(r);
int ans = cnt - sl[l - 1] - sr[r + 1];
if((s[0][l] == '1' && s[1][l] == '0' && s[2][l] == '1') && nxt[l] > r) {
write(2);
continue;
}
if((s[0][l] == '1' && s[1][l] == '0' && s[2][l] == '1' && id[0][l] == id[2][l]) && check(nxt[l])) ans ++;
if((s[0][r] == '1' && s[1][r] == '0' && s[2][r] == '1' && id[0][r] == id[2][r]) && check(las[r])) ans ++;
write(ans);
}
return 0;
}
CF1661F Teleporters
一眼发现与分段有关系,由均值不等式得知对一段添加 \(x\) 个点怎样加最优。
注意到一个段里的点随着越来越多,对当前答案的优化程度会越来越少。
得到一个与答案大小相关的做法,即每次选择对答案变化量最大的一段添加一个点,直到答案小于 \(m\)。
很明显很不优秀,注意到可以二分最低添加一个点的减量,把每一段都一直加点直到减量小于二分值。
加点数量也可以二分。
最后算出答案,其中最低减量为 \(d\) ,但是可能有些减量为 \(d\) 的加点是不用实施的,因此减去这些点就好了。
略微卡常。
inline LL f(LL d , LL t) {
if(!t) return 5e18;
LL k = d / t , num = d % t;
return k * k * t + (k * 2 + 1) * num;
}
inline pll check(LL x) {
pll res = mp(0 , 0);
for (int i = 1; i <= n; ++i) {
LL d = a[i] - a[i - 1];
int l = 0 , r = d;
pll now = mp(m + 1 , m + 1);
while(l <= r) {
int mid = (l + r) >> 1;
LL tmp = f(d , mid + 1);
LL cur = f(d , mid) - tmp;
if(cur >= x) l = mid + 1 , now = mp(tmp , mid);
else r = mid - 1;
}
res.fs += now.fs , res.sc += now.sc;
if(res.fs > m) return res;
}
return res;
}
int main() {
// freopen("1.in" , "r" , stdin);
read(n);
for (int i = 1; i <= n; ++i) read(a[i]);
read(m);
LL l = 0 , r = m , now = 0;
pll res = mp(0 , 0);
while(l <= r) {
LL mid = (l + r) >> 1;
pll cur = check(mid);
if(cur.fs <= m) res = cur , now = mid , l = mid + 1;
else r = mid - 1;
}
res.sc -= (m - res.fs) / now;
write(res.sc);
return 0;
}