这场 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;
}