Educational Codeforces Round 28
A. Curriculum Vitae
因为1
之后不能出现0
,所以求一下前缀0
的个数和后缀1
的个数,然后枚举第一个1
的位置即可
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main() {
ios::sync_with_stdio(false) , cin.tie(nullptr) , cout.tie(nullptr);
int n;
cin >> n;
vector<int> a(n) , b(n) , c(n);
for( auto & i : a ) cin >> i;
b[0] = ( a[0] == 0 );
for( int i = 1 ; i < n ; i ++ )
b[i] = b[i-1] + ( a[i] == 0 );
c[n-1] = a[n-1];
for( int i = n-2 ; i >= 0 ; i -- )
c[i] = c[i+1] + a[i];
int res = max( b.back() , c.front() );
for( int i = 1 ; i < n ; i ++ )
res = max( res , b[i-1] + c[i] );
cout << res;
return 0;
}
B. Math Show
先枚举几组,剩下的贪心选就好了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main(){
int n , m , k , res = 0 , cnt = 0;
cin >> n >> m >> k;
vector<int> a(m);
for( auto & i : a )
cin >> i , cnt += i;
sort( a.begin() , a.end() );
for( int i = 0 , ans , p ; i <= n && i * cnt <= k ; i ++ ){
ans = i * (m+1) , p = k - i * cnt;
for( auto j : a){
int t = min( n - i , p / j );
ans += t , p -= t * j ;
}
res = max( res , ans );
}
cout << res << "\n";
return 0;
}
C. Four Segments
把三个分割符记为\(x,y,z\),\(res=sum(0,x)-sum(x,y)+sum(y,z)-sum(z,n)\)
然后发现当\(y\)确定时,\(x,z\)的选择相互没有影响,所以可以\(O(N^2)\)的计算对于每一个\(y\)最优的\(sum(0,x)-sum(x,y)\)的\(x\)出现在哪里,\(z\)同理,最后枚举一下选择哪个\(y\)即可
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int n;
cin >> n;
vector<int> v(n), pre(n);
for (auto &i: v) cin >> i;
pre[0] = v[0];
for (int i = 1; i < n; i++)
pre[i] = pre[i - 1] + v[i];
auto f = [pre](int l, int r) {
if (l == r) return 0ll;
if (l == 0) return pre[r - 1];
return pre[r - 1] - pre[l - 1];
};
vector<int> ya(n + 1), yx(n + 1), yb(n + 1), yz(n + 1);
for (int y = 0; y <= n; y++) {
ya[y] = f(0, y), yx[y] = y;
for (int x = y - 1, a; x >= 0; x--) {
a = f(0, x) - f(x, y);
if (a > ya[y]) ya[y] = a, yx[y] = x;
}
}
for (int y = 0; y <= n; y++) {
yb[y] = f(y, n), yz[y] = n;
for (int z = n - 1, b; z >= y; z--) {
b = f(y, z) - f(z, n);
if (b > yb[y]) yb[y] = b, yz[y] = z;
}
}
int rv = LLONG_MIN, rx, ry, rz;
for (int y = 0, yv; y <= n; y++) {
yv = ya[y] + yb[y];
if (yv > rv) rv = yv, rx = yx[y], ry = y, rz = yz[y];
}
cout << rx << " " << ry << " " << rz << "\n";
return 0;
}
D. Monitor
其实就是一个矩形一开始是全部是正无穷,然后修改某些点的值,询问所有\(k\times k\)的子矩形的最大值最小是多少。
首先可以滑动窗口的方式计算出每一行所有长度为\(k\)的子区间的最大值,把这些结果全部存到一个数组中,再用滑动窗口计算每一个\(k\times k\)的子矩形的最大值,最后取最小值即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int n, m, k, q;
cin >> n >> m >> k >> q;
vector<vector<int>> a(n + 1, vector<int>(m + 1, LLONG_MAX));
for (int x, y, z; q; q--)
cin >> x >> y >> z, a[x][y] = z;
vector<vector<int>> b(n + 1, vector<int>(m + 1));
for (int i = 1; i <= n; i++) {
multiset<int> s;
for (int j = 1; j < k; j++)
s.insert(a[i][j]);
for (int j = k; j <= m; j++) {
s.insert(a[i][j]);
b[i][j] = *s.rbegin();
s.erase(s.lower_bound(a[i][j - k + 1]));
}
}
int res = LLONG_MAX;
for (int j = k; j <= m; j++) {
multiset<int> s;
for (int i = 1; i < k; i++)
s.insert(b[i][j]);
for (int i = k; i <= n; i++) {
s.insert(b[i][j]);
res = min(res, *s.rbegin());
s.erase(s.lower_bound(b[i - k + 1][j]));
}
}
if (res == LLONG_MAX) cout << "-1\n";
else cout << res << "\n";
return 0;
}
E. Chemistry in Berland
树形DP,\(f[i]\)表示节点\(i\)在满足子树内全部够用的情况下还余/欠多少,递归的计算出每个节点的子节点的\(f[i]\)多余的就转化给父节点,不够就从父节点转换,最后检查根节点是否够用即可。
要注意的是转换的过程中很容易爆long long
,最大值应该是\(10^{9\times10^5}\),所以要用些特殊的方法来判断。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
int n;
vector<int> a, b, f;
vector<vector<pair<int, int>>> e;
constexpr int inf = 1e18;
int read() {
int x = 0, f = 1, ch = getchar();
while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
if (ch == '-') f = -1, ch = getchar();
while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
void dfs(int x) {
f[x] = b[x] - a[x];
for (auto [y, v]: e[x]) {
dfs(y);
if (f[y] < 0) {
if( inf / v < - f[y] )
f[x] = - inf;
else f[x] += f[y] * v, f[y] = 0;
if( f[x] < -inf ) f[x] = -inf;
}
else f[x] += f[y], f[y] = 0;
}
}
int32_t main() {
n = read();
a = vector<int>(n + 1), b = vector<int>(n + 1);
f = vector<int>(n + 1);
for (int i = 1; i <= n; i++) b[i] = read();
for (int i = 1; i <= n; i++) a[i] = read();
e = vector<vector<pair<int, int>>>(n + 1);
for (int i = 2, fa, v; i <= n; i++)
fa = read(), v = read(), e[fa].emplace_back(i, v);
dfs(1);
if (f[1] >= 0) printf("YES\n");
else printf("NO\n");
return 0;
}
F. Random Query
题目期望是假的,实际上我们只要计算所有的子区间中的数的种类数之和最后除以子区间数即可。
要计算区间内数的种类显然不可取,因为至少有\(O(N^2)\)的复杂度。规定区间内每种数最后一次出现有效,这样的话我们来考虑每个数在哪些区间里面会产生贡献。我们计算出\(nxt[i]\)表示\(i\)下一次出现的位置,那么数字\(i\)向左一直到\(1\)都可以产生贡献,向右到\(nxt[i]-1\)都可以产生贡献,所有\(i\)产生贡献的区间有\(i\times(nex[i]-i)\)
注意\(l>r\)也可以产生贡献,但是\(l=r\)只能产生一次贡献。
#include <bits/stdc++.h>
using namespace std;
#define int long long
int read() {
int x = 0, f = 1, ch = getchar();
while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
if (ch == '-') f = -1, ch = getchar();
while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
int32_t main() {
int n = read() , res = 0;
vector<int> a(n+1) , nxt( 1e6+1 , n+1 );
for( int i = 1 ; i <= n ; i ++ )
a[i] = read();
for( int i = n ; i >= 1 ; i -- )
res += i * (nxt[a[i]] - i) , nxt[a[i]] = i;
res = res * 2 - n;
printf("%.6lf\n" , (double) res / double(n*n) );
return 0;
}