总结
ABC没什么说的,D体面没读明白,vp的时候没做出来。E题计算lca的时候少跳了一步,调了很久才发现,说明对板子还是有些生疏。从F题学到了线段树的一种类似二分的查询方法,学到了set的erase函数返回值是删掉的迭代器的下一个。
A. USB Flash Drives
题意
给你一些U盘,每个U盘能存储一定大小的数据,求存储一定大小的数据至少需要多少U盘。
思路
将U盘从大到小排序,贪心选就是最优的答案。复杂度 O ( n log n ) O(n \log n) O(nlogn) 。
代码
#include <bits/stdc++.h>
using namespace std;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n, m; cin >> n >> m;
vector<int> a(n);
for(int i = 0; i<n; i++) cin >> a[i];
sort(a.begin(), a.end());
reverse(a.begin(), a.end());
int cnt = 0;
for(int i = 0; i<n; i++) {
if(m - a[i] > 0) {
m -= a[i];
cnt++;
} else {
cout << cnt+1 << "\n";
break;
}
}
return 0;
}
B. The Best Gift
题意
给 n n n ( n ≤ 2 ⋅ 1 0 5 ) (n \leq 2 \cdot 10^5) (n≤2⋅105) 本书,每本有一个种类 (种类的数量不超过 10 10 10),现在想选两本不同种类的书,求一共有多少种选法。
思路
开一个桶,记录每种书籍出现的次数,再暴力枚举种类,计算方案数量。复杂度 O ( n + m 2 ) O(n + m^2) O(n+m2) 。
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n, m; cin >> n >> m;
vector<int> cnt(m+1);
for(int i = 0; i<n; i++) {
int x; cin >> x; cnt[x]++;
}
vector<int> v;
for(int i = 1; i<=m; i++) if(cnt[i]) v.push_back(cnt[i]);
ll ans = 0;
for(int i = 0; i<v.size(); i++) {
for(int j = i+1; j<v.size(); j++) {
ans += v[i]*v[j];
}
}
cout << ans << "\n";
return 0;
}
C. Load Balancing
题意
给一个长度为 n n n ( n ≤ 1 0 5 ) (n \leq 10^5) (n≤105) 的序列,可以使用一代价,让其中一个值加一,另一个值减一。求使序列极差最小需要的最小代价。
思路
将原序列 a a a 升序排列,创建一个新序列 b b b 为目标序列,同样的要保证升序。 i i i 从 1 1 1 到 n n n 枚举,如果 a i > b i a_i \gt b_i ai>bi,把么就需要 a i − b i a_i - b_i ai−bi 的代价将 a i a_i ai 减为 b i b_i bi 。如果 a i < b i a_i \lt b_i ai<bi,就不需要考虑,因为值增加与值减少是对应的,只需要考虑一种即可。复杂度 O ( n log n ) O(n \log n) O(nlogn) 。
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n; cin >> n;
vector<int> a(n);
for(int i = 0; i<n; i++) cin >> a[i];
sort(a.begin(), a.end());
int sum = accumulate(a.begin(), a.end(), 0);
vector<int> b(n, sum / n);
for(int i = n-1; i>n-1-sum%n; i--) b[i]++;
int ans = 0;
for(int i = 0; i<n; i++) if(a[i] > b[i]) ans += a[i] - b[i];
cout << ans << "\n";
return 0;
}
D. Gadgets for dollars and pounds
题意
总共有 m m m ( m ≤ 2 ⋅ 1 0 5 ) (m \leq 2 \cdot 10^5) (m≤2⋅105) 种商品需要你从中购买 k k k ( k ≤ m ) (k \leq m) (k≤m) 个,你有 s s s ( m ≤ 1 0 9 ) (m \leq 10^9) (m≤109) 个金币。每种商品可以使用一定数量的美元或英镑购买。一共有 n n n ( n ≤ 2 ⋅ 1 0 5 ) (n \leq 2 \cdot 10^5) (n≤2⋅105) 天,每天有不同的金币兑换美元英镑的汇率。求至少需要多少天才能买到 k k k 个商品。
思路
显然具有两段性,可以二分答案。下面考虑对于一个答案 x x x 如何去检查是否可行。
先找到前 x x x 天美元英镑汇率的最小值。如果我们要购买需要使用美元购买的商品,那么一定是在前 x x x 天美元汇率最低的一天进行购买。英镑同理。根据美元英镑的最低汇率,将商品的代价均转化为金币,按金币的价格升序排列,从小到大贪心计算最多能购买多少商品。
复杂度 O ( n log 2 n ) O(n \log^2n) O(nlog2n) 。
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 2e5+20;
int a[maxn], b[maxn];
int t[maxn], c[maxn];
pair<int, int> ans[maxn];
int n, m, k, s;
bool check(int x) {
int posa = min_element(a, a+x) - a;
int posb = min_element(b, b+x) - b;
// cerr << posa << " " << posb << endl;
vector<pair<ll, int>> v;
for(int i = 0; i<m; i++) {
if(t[i] == 1) v.emplace_back((ll) c[i] * a[posa], i);
else v.emplace_back((ll) c[i] * b[posb], i);
}
sort(v.begin(), v.end());
ll need = 0;
for(int i = 0; i<k; i++) need += v[i].first;
if(need <= s) {
for(int i = 0; i<k; i++){
int p = v[i].second;
if(t[p] == 1) ans[i] = {posa, p};
else ans[i] = {posb, p};
}
return true;
} else return false;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m >> k >> s;
for(int i = 0; i<n; i++) cin >> a[i];
for(int i = 0; i<n; i++) cin >> b[i];
for(int i = 0; i<m; i++) cin >> t[i] >> c[i];
if(!check(n)) {
cout << -1 << "\n";
return 0;
}
int l = 1, r = n;
while(l < r) {
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid+1;
}
cout << l << "\n";
for(int i = 0; i<k; i++) cout << ans[i].second+1 << " " << ans[i].first+1 << "\n";
return 0;
}
E. Minimum spanning tree for each edge
题意
给定简单无向图,边数点数最多均为 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105,对于每一条边,求包含这条边的最小生成树边权和。
思路
先求出这个图的最小生成树边权和。对于每一条边,看作一次询问。如果这条边恰好在最小生成树上,直接输出整张图的最小生成树边权和;如果不在,需要在原图的最小生成树上将这条边加上,这样就会出现一个环,再将环上的最大边权的边减去,得到的就是包含这条边的最小生成树。
考虑代码实现。不需要建立原图。读取每一条边,使用kruskal算法求出给定图的最小生成树,并将生成树建立出来。再跑一遍lca算法,不仅要处理出每个点向上跳到的祖先,还需要处理出每个点向上跳到某个祖先路径上的最大边权。对于不在生成树上的边 u − v u - v u−v 的询问,假设将这条边加到树上,环一定是由 u − l c a u - lca u−lca、 l c a − v lca - v lca−v、 v − u v - u v−u,构成的,那么我们删去的边就是 u u u 到 l c a lca lca 与 v v v 到 l c a lca lca 路径上的最长的一条。
复杂度 O ( n log n + m log m ) O(n \log n + m \log m) O(nlogn+mlogm) 。
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 2e5+20;
const int maxm = 4e5+20;
int h[maxn], e[maxm], w[maxm], ne[maxm], top;
void add(int a, int b, int c) {
e[top] = b, w[top] = c, ne[top] = h[a], h[a] = top++;
}
ll ans[maxn];
int n, m;
int p[maxn];
int find(int x) {
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
struct Edge {
int id, x, y, w;
bool used;
bool operator < (const Edge& o) const {
return w < o.w;
}
} edges[maxm];
ll kruskal() {
ll ret = 0;
sort(edges, edges+m);
for(int i = 0; i<m; i++) {
Edge& e = edges[i];
int x = e.x, y = e.y, w = e.w;
int px = find(x), py = find(y);
if(px != py) {
e.used = true;
add(x, y, w); add(y, x, w);
p[px] = py;
ret += w;
}
}
return ret;
}
int depth[maxn];
int fa[maxn][19];
int mx[maxn][19];
void bfs(int root){
memset(depth, 0x3f, sizeof depth);
depth[0] = 0; depth[root] = 1;
queue<int> q; q.push(root);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if(depth[v] > depth[u] + 1){
depth[v] = depth[u] + 1;
q.push(v);
fa[v][0] = u, mx[v][0] = w[i];
for(int k = 1; k<=18; k++)
fa[v][k] = fa[fa[v][k-1]][k-1], mx[v][k] = max(mx[v][k-1], mx[fa[v][k-1]][k-1]);
}
}
}
}
int query(int a, int b){
int ret = 0;
if(depth[a] < depth[b]) swap(a, b);
for(int k = 18; k>=0; k--)
if(depth[fa[a][k]] >= depth[b])
ret = max(ret, mx[a][k]), a = fa[a][k];
if(a == b) return ret;
for(int k = 18; k>=0; k--)
if(fa[a][k] != fa[b][k]) {
ret = max({ret, mx[a][k], mx[b][k]});
a = fa[a][k], b = fa[b][k];
}
return max({ret, mx[a][0], mx[b][0]});
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
memset(h, -1, sizeof h);
for(int i = 0; i<maxn; i++) p[i] = i;
cin >> n >> m;
for(int i = 0; i<m; i++) {
Edge& e = edges[i]; e.id = i;
cin >> e.x >> e.y >> e.w;
}
ll tot = kruskal();
bfs(1);
for(int i = 0; i<m; i++) {
Edge& e = edges[i];
if(e.used) ans[e.id] = tot;
else ans[e.id] = tot + e.w - query(e.x, e.y);
}
for(int i = 0; i<m; i++) cout << ans[i] << "\n";
return 0;
}
F. Frogs and mosquitoes
题意
给 n n n 只青蛙,坐落在数轴上,每只青蛙有位置 x x x、舌头长度 t t t 两个属性。给一个蚊子序列,蚊子有位置 p p p 、大小 b b b 两个属性。现在所有蚊子从前到后依次尝试降落。对于一只蚊子,如果有一只青蛙满足 x + t > = p x + t >= p x+t>=p 和 x < = p x <= p x<=p 两个条件,这个青蛙就会吃掉这只蚊子,同时舌头长度会增加 b b b 。如果长度增加使得青蛙能够吃到停留在坐标轴上的新的蚊子,青蛙就会继续吃下去。对于一只蚊子,如果多个青蛙都能吃到,那么最左边的青蛙会吃掉它;如果没有青蛙能吃到,那么这只蚊子就会降落到坐标轴上它的位置。
思路
使用线段树维护每只青蛙能够吃到的最远点。用一个multiset维护降落到坐标轴上的蚊子。从前至后枚举蚊子序列,找到第一个能吃它的青蛙,这只青蛙将它吃掉后,不断地吃坐标轴上的其它蚊子。如果没有青蛙能够吃掉这只蚊子,就将蚊子放进multiset中。复杂度 O ( n log n + m log m ) O(n \log n + m \log m) O(nlogn+mlogm) 。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+20;
int n, m;
struct Node {
int x, t, id;
bool operator < (const Node &o) const {
return x < o.x;
}
} a[maxn];
int sum[maxn], ans[maxn];
int val[maxn * 4];
void build(int u, int l, int r) {
if(l == r) val[u] = a[l].x + a[l].t;
else {
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid+1, r);
val[u] = max(val[u << 1], val[u << 1 | 1]);
}
}
int query(int u, int l, int r, int x) {
if(l == r) return l;
int mid = l + r >> 1;
if(val[u << 1] >= x) return query(u << 1, l, mid, x);
else return query(u << 1 | 1, mid+1, r, x);
}
void modify(int u, int l, int r, int x, int v) {
if(l == r) val[u] = v;
else {
int mid = l + r >> 1;
if(mid >= x) modify(u << 1, l, mid, x, v);
else modify(u << 1 | 1, mid+1, r, x, v);
val[u] = max(val[u << 1], val[u << 1 | 1]);
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for(int i = 1; i<=n; i++) cin >> a[i].x >> a[i].t, a[i].id = i;
sort(a+1, a+1+n);
build(1, 1, n);
multiset<pair<int, int> > st;
for(int i = 1; i<=m; i++) {
int p, b; cin >> p >> b;
int x = query(1, 1, n, p);
if(a[x].x + a[x].t < p || a[x].x > p) st.insert({p, b});
else {
auto it = st.lower_bound({a[x].x + a[x].t, 0});
a[x].t += b;
sum[a[x].id] ++;
while(it != st.end() && it->first <= a[x].x + a[x].t) {
a[x].t += it->second;
sum[a[x].id]++;
it = st.erase(it);
}
modify(1, 1, n, x, a[x].x + a[x].t);
}
}
for(int i = 1; i<=n; i++) ans[a[i].id] = a[i].t;
for(int i = 1; i<=n; i++) cout << sum[i] << " " << ans[i] << "\n";
return 0;
}