文章目录
A. Upload More RAM
Problem
你每秒都可以上传0GB或1GB的内存,但是每 k k k 秒最多上传1GB的内存。
问将 n n n GB的内存全部上传需要的最短时间。
数据范围: 1 ≤ n , k ≤ 100 1 \leq n,k \leq 100 1≤n,k≤100
Solution
最优的方案是第一秒的时候上传1GB,之后每 k k k 秒上传1GB。答案即为 ( n − 1 ) × k + 1 (n-1)\times k+1 (n−1)×k+1
时间复杂度: O ( 1 ) O(1) O(1)
Code
void solve()
{
cin >> n >> k;
printf("%d\n", (n - 1) * k + 1);
}
B. K-Sort
Problem
给定一个数组 { a } i = 1 n \{a\}_{i=1}^n {a}i=1n,每次可以花费 k + 1 k+1 k+1 枚硬币,从数组中选取 k k k 个元素,并执行 a i = a i + 1 a_i=a_i+1 ai=ai+1 。
问最少需要多少枚硬币,使得数组 { a } i = 1 n \{a\}_{i=1}^n {a}i=1n 是非递减。
数据范围: 1 ≤ n ≤ 1 0 5 , 1 ≤ a i ≤ 1 0 9 1\leq n \leq 10^5,1\leq a_i \leq 10^9 1≤n≤105,1≤ai≤109
Solution
假设数组 { b } i = 1 n \{b\}_{i=1}^n {b}i=1n 是得到的非递减数组,那么 b i b_i bi就是原数组前缀中最大的元素,可以通过 b i = m a x ( b i − 1 , a i ) b_i=max(b_{i-1},a_i) bi=max(bi−1,ai) 得到。那么每个位置需要执行的操作次数为 d i = b i − a i d_i=b_i-a_i di=bi−ai,将其记为数组 { d } i = 1 n \{d\}_{i=1}^n {d}i=1n,并对其进行从小到大的排序。
答案即为 a n s = ∑ ( d i − d i − 1 ) × ( n − i + 1 ) ans=\sum (d_i-d_{i-1})\times (n-i+1) ans=∑(di−di−1)×(n−i+1),其中 d i − d i − 1 d_i-d_{i-1} di−di−1为需要重复的次数, n − i + 1 n-i+1 n−i+1为每次操作需要的硬币。
时间复杂度: O ( n l o g n ) O(n\ log\ n) O(n log n)
Code
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
int ma = a[1];
vector<int> b;
b.push_back(0);
for (int i = 2; i <= n; i++)
{
ma = max(ma, a[i]);
b.push_back(ma - a[i]);
}
sort(b.begin(), b.end());
ll ans = 0;
for (int i = 1; i < n; i++)
{
ans += (ll)(b[i] - b[i - 1]) * (n - i + 1);
}
printf("%lld\n", ans);
}
C. Basil’s Garden
Problem
给定一个数组 { h } i = 1 n \{h\}_{i=1}^n {h}i=1n,每秒钟,如果 i = n i=n i=n 或者 h i > h i + 1 h_i > h_{i+1} hi>hi+1,那么执行 h i = m a x ( 0 , h i − 1 ) h_i=max(0,h_i-1) hi=max(0,hi−1)。
问几秒钟后, h i = 0 ( 1 ≤ i ≤ n ) h_i=0\ (1\leq i\leq n) hi=0 (1≤i≤n) 的情形第一次出现。
数据范围: 1 ≤ n ≤ 1 0 5 , 1 ≤ h i ≤ 1 0 9 1\leq n \leq 10^5,1\leq h_i \leq 10^9 1≤n≤105,1≤hi≤109
Solution
记该数组从 h i , ⋯ , h n h_i,\cdots, h_n hi,⋯,hn 全部变为零需要的时间为 a i a_i ai。
如果 h i − 1 ≤ a i h_{i-1}\leq a_i hi−1≤ai,那么存在某一时刻,使得 h i − 1 = h i h_{i-1}=h_i hi−1=hi,当经过 a i a_i ai 秒后,原数组 h i − 1 , h i , ⋯ h n h_{i-1},h_i,\cdots h_n hi−1,hi,⋯hn 变为 1 , 0 , ⋯ , 0 1,0,\cdots,0 1,0,⋯,0,此时 a i − 1 = a i + 1 a_{i-1}=a_i+1 ai−1=ai+1。
如果 h i − 1 > a i h_{i-1}>a_i hi−1>ai,经过 a i a_i ai秒后,原数组 h i − 1 , h i , ⋯ h n h_{i-1},h_i,\cdots h_n hi−1,hi,⋯hn 变为 h i − 1 − a i , 0 , ⋯ , 0 h_{i-1}-a_i,0,\cdots,0 hi−1−ai,0,⋯,0,此时 a i − 1 = h i − 1 a_{i-1}=h_{i-1} ai−1=hi−1。
整理后可以得到 a i − 1 = m a x ( a i + 1 , h i − 1 ) a_{i-1}=max(a_i+1,h_{i-1}) ai−1=max(ai+1,hi−1)
时间复杂度: O ( n ) O(n) O(n)
Code
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
ll ans = 0;
for (int i = n; i >= 1; i--)
{
if (a[i] <= ans)
{
ans++;
}
else
{
ans = a[i];
}
}
printf("%lld\n", ans);
}
D. World is Mine
Problem
给定一个数组 { A } i = 1 n \{A\}_{i=1}^n {A}i=1n,Alice和Bob轮流取走其中的元素,Alice先取,如果当前玩家不能取任何元素,那么游戏结束。
Alice每次取走的元素必须严格大于Alice上一次取走的元素,而Bob可以从剩下的元素中任选一个。
记 x x x为Alice取走的个数,Alice希望 x x x越大越好,Bob希望 x x x越小越好。问最后 x x x为多少,如果两人都采取最优策略。
数据范围: 1 ≤ n ≤ 5000 , 1 ≤ a i ≤ n 1\leq n \leq 5000,1\leq a_i \leq n 1≤n≤5000,1≤ai≤n
Solution
记 a i a_i ai 表示第 i i i大的值出现的次数。对于Alice来说,最佳的策略显然就是从小到大的取,而对Bob来说,就是在满足条件的情况下使得更多的 a k a_k ak变为0。
记 f i , j f_{i,j} fi,j 表示Bob在前 i i i 大的值中取走 j j j 个元素能使 a k = 0 a_k=0 ak=0的最大个数。状态初值设为 − ∞ -\infty −∞, f 0 , 0 = 0 f_{0,0}=0 f0,0=0。
状态转移过程:
- f i , j = m a x ( f i , j , f i − 1 , j ) f_{i,j}=max(f_{i,j},f_{i-1,j}) fi,j=max(fi,j,fi−1,j),这是不将 a i a_i ai改为0的情形。
- 当 i − f i − 1 , j − a i − 1 ≥ j i-f_{i-1,j-a_i}-1\geq j i−fi−1,j−ai−1≥j时, f i , j = m a x ( f i , j , f i − 1 , j − a i + 1 ) f_{i,j}=max(f_{i,j},f_{i-1,j-a_i}+1) fi,j=max(fi,j,fi−1,j−ai+1)。这是将 a i a_i ai改为0的情形,需要满足Alice拿走的个数( i − f i − 1 , j − a i − 1 i-f_{i-1,j-a_i}-1 i−fi−1,j−ai−1 )大于等于Bob拿走的个数( j j j )。
最后的答案为 m − m a x ( f m , k ) m-max(f_{m,k}) m−max(fm,k),其中 m m m表示数组 { A } i = 1 n \{A\}_{i=1}^n {A}i=1n中不同元素的个数。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
Code
void solve()
{
cin >> n;
for (int i = 0; i <= n; i++)
{
cnt[i] = 0;
for (int j = 0; j <= n; j++)
{
f[i][j] = -1e9;
}
}
for (int i = 1; i <= n; i++)
{
int a;
cin >> a;
cnt[a]++;
}
vector<int> a;
a.push_back(0);
for (int i = 1; i <= n; i++)
{
if (cnt[i])
{
a.push_back(cnt[i]);
}
}
int m = a.size() - 1;
f[0][0] = 0;
for (int i = 1; i <= m; i++)
{
for (int j = 0; j <= i; j++)
{
f[i][j] = f[i - 1][j];
if (j >= a[i] && i - j >= f[i - 1][j - a[i]] + 1)
{
f[i][j] = max(f[i][j], f[i - 1][j - a[i]] + 1);
}
// printf("%d ", f[i][j]);
}
// printf("\n");
}
int ans = 0;
for (int i = 1; i <= m; i++)
{
ans = max(ans, f[m][i]);
}
printf("%d\n", m - ans);
}
E. Wonderful Tree!
Problem
给定一棵 n n n个顶点的树,根为顶点1,同时每个顶点都有值 a i a_i ai。
一棵好树需要满足对于所有顶点 v v v, a v ≤ ∑ a u a_v\leq \sum a_u av≤∑au,其中顶点 u u u是顶点 v v v的直接孩子,即存在一条边连接 u u u和 v v v。
每次操作可以使得一个顶点的值加一,问最少需要多少次操作才能使得该棵树为好树。
数据范围: 2 ≤ n ≤ 5000 , 0 ≤ a i ≤ 1 0 9 2\leq n\leq 5000,0\leq a_i\leq 10^9 2≤n≤5000,0≤ai≤109
Solution
记 b u = ∑ a v − a u b_u=\sum a_v -a_u bu=∑av−au,叶子节点记为 + ∞ +\infty +∞, h i h_i hi 为顶点的深度。问题转化成使得 b i ≥ 0 ( 1 ≤ i ≤ n ) b_i\geq0\ (1\leq i\leq n) bi≥0 (1≤i≤n) 需要的最小操作次数。
先从特殊情况开始考虑,假设此时是以 u u u为根的树,其子树均为好树,只有 b u < 0 b_u<0 bu<0。
若节点 u u u 的孩子 b v > 0 b_v>0 bv>0,那么 b u b_u bu 每次加一只需要操作一次,一共能加 b v b_v bv 次;而如果 b v = 0 b_v=0 bv=0,那么 b u b_u bu 要想加一,就必须顺着树传递下去,对路径上的节点依次执行加一操作,直到找到 b i > 0 b_i>0 bi>0 的节点,需要执行的操作次数为 h i − h u h_i-h_u hi−hu 。
因此要使得该树变成好树的最优方案就是按 b u b_u bu 加一需要的代价从小到大执行,直到 b u b_u bu 恰好等于0。
对于一般的情况可以先从叶子节点开始往根节点遍历,这样每次遇到的都是上述的特殊情况。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
Code
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
vector<int> g[n + 1], h(n + 1, 0);
for (int i = 2; i <= n; i++)
{
int p;
cin >> p;
g[p].push_back(i);
h[i] = h[p] + 1;
}
for (int i = 1; i <= n; i++)
{
if (g[i].empty())
{
b[i] = inf;
continue;
}
b[i] = -a[i];
for (auto u : g[i])
{
b[i] += a[u];
}
// printf("%lld ", b[i]);
}
// printf("\n");
ll ans = 0;
for (int i = n; i >= 1; i--)
{
queue<int> q;
q.push(i);
if (b[i] >= 0)
{
continue;
}
while (!q.empty())
{
int u = q.front();
q.pop();
for (auto v : g[u])
{
if (b[v] > 0)
{
int d = min(-b[i], b[v]);
b[i] += d;
b[v] -= d;
ans += (ll)d * (h[v] - h[i]);
}
q.push(v);
}
}
}
printf("%lld\n", ans);
}