文章目录
竞赛首页 Codeforces Round #661 (Div. 3)
A - Remove Smallest
题意
对于 ∣ a i − a j ∣ ≤ 1 ( i ≠ j ) | a_i - a_j| \leq 1(i \not= j) ∣ai−aj∣≤1(i=j),可以删去较小的一个数
问该数组最后是否能只留下一个元素
分析
直接将数组排个序,然后判断是否与前一位的差值为 1 1 1
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100 + 5;
int a[maxn];
int main(){
int T;
scanf("%d", &T);
while(T--) {
int n, f = 1;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
for(int i = 2; i <= n; ++i) {
if(abs(a[i] - a[i - 1]) > 1) f = 0;
}
puts(f ? "YES" : "NO");
}
return 0;
}
B - Gifts Fixing
题意
每次可以对数组进行如下操作
- a i − 1 a_i - 1 ai−1
- b i − 1 b_i - 1 bi−1
- a i − 1 , b i − 1 a_i - 1, b_i - 1 ai−1,bi−1
问至少需要多少次操作,才可以使所有 a i a_i ai 都相等,所有 b i b_i bi 都相等
分析
分别以最小的 a i , b i a_i, b_i ai,bi 为基准,计算每个 a i , b i a_i, b_i ai,bi 需要改变的量
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 100 + 5;
ll a[maxn];
ll b[maxn];
int main(){
int T;
scanf("%d", &T);
while(T--) {
int n; ll mi[2];
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
for(int i = 1; i <= n; ++i) scanf("%lld", &b[i]);
mi[0] = mi[1] = inf;
for(int i = 1; i <= n; ++i)
mi[0] = min(mi[0], a[i]), mi[1] = min(mi[1], b[i]);
ll ans = 0, x;
for(int i = 1; i <= n; ++i) {
x = min(a[i]-mi[0], b[i]-mi[1]);
ans += a[i] - x - mi[0] + b[i] - x - mi[1] + x;
}
printf("%lld\n", ans);
}
return 0;
}
C - Boats Competition [枚举]
题意
每个 a i a_i ai 只能选择一次,求使得 a i + a j = s a_i + a_j = s ai+aj=s 这样的对数最多的 s s s
分析
记录每个值出现的次数,对于每个值 a i a_i ai,和为 s s s 仅有一种情况即 a j = s − a i a_j = s-a_i aj=s−ai
直接枚举 s s s 的值,计算每次 s s s 所能得到的最大对数
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 100 + 5;
int cnt[maxn];
int main(){
int T;
scanf("%d", &T);
while(T--) {
int n;
scanf("%d", &n);
memset(cnt, 0, sizeof(cnt));
for(int i = 1, x; i <= n; ++i) {
scanf("%d", &x); cnt[x] += 1;
}
int res = 0;
for(int i = 2; i <= 100; ++i) {
int ans = 0, r = i / 2;
for(int j = 1; j <= r; ++j)
ans += min(cnt[j], cnt[i - j]);
if(i % 2 == 0) ans = ans - cnt[r] + cnt[r]/2;
res = max(res, ans);
}
printf("%d\n", res);
}
return 0;
}
D - Binary String To Subsequences [队列] ★
题意
给出一个长度为 n n n,且仅含有 0, 1 的字符串,对这个串取任意位置字符且不改变字符次序可以构成他的子串
问该串最少由多少个形如 “101010…” 或者 “010101…” 这样的串构成,并且输出每个字符串所在的应该是第几个子串
如果有多种情况输出任意一种即可
分析
对于当前串,可以考虑现在是以 1 1 1 结尾还是以 0 0 0 结尾
如果 s [ i ] = = ′ 1 ′ s[i] == '1' s[i]==′1′,那么现在这个 s [ i ] s[i] s[i] 就要接到以 0 0 0 结尾的串后面
如果 s [ i ] = = ′ 0 ′ s[i] == '0' s[i]==′0′,那么现在这个 s [ i ] s[i] s[i] 就要接到以 1 1 1 结尾的串后面
用vector存放每个 i i i,队列模拟
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 2e5 + 5;
queue<vector<int> > q[2];
char s[maxn];
int a[maxn];
int cnt;
void solve(int x, int i) {
vector<int> v; int k = x^1;
if(!q[k].empty()) {
v = q[k].front(); q[k].pop();
}
v.push_back(i);
q[x].push(v);
}
int get(int x) {
vector<int> v;
while(!q[x].empty()) {
v = q[x].front(); q[x].pop();
cnt += 1;
for(auto i : v) a[i] = cnt;
}
}
int main(){
int T;
scanf("%d", &T);
while(T--) {
int n;
scanf("%d%s", &n, s + 1);
while(!q[0].empty()) q[0].pop();
while(!q[1].empty()) q[1].pop();
for(int i = 1; i <= n; ++i) {
solve(s[i] - '0', i);
}
cnt = 0;
get(0), get(1);
printf("%d\n", cnt);
for(int i = 1; i <= n; ++i)
printf("%d%c", a[i], i == n ? '\n' : ' ');
}
return 0;
}
E1 - Weights Division (easy version) [优先队列][排序] ★★
题意
给一个有根树, 1 1 1 为根节点,叶子节点就是没有子节点的点
每条边 ( u , v ) (u, v) (u,v) 有一个权值,每次可以选择任意一条边,使这条边的权值变为 ⌊ w i 2 ⌋ \lfloor \frac{w_i}{2} \rfloor ⌊2wi⌋
要使 1 1 1 到所有叶子节点的路径和 ≤ S \leq S ≤S,问至少需要执行上次操作多少次
分析
计算每条边的贡献,边的贡献 = = =边的权值 × \times ×边经过的次数
所以每次改变一条边,所改变的贡献值也可以直接计算出 ( w i − ⌊ w i 2 ⌋ ) × c n t i (w_i - \lfloor \frac{w_i}{2} \rfloor) \times cnt_i (wi−⌊2wi⌋)×cnti
每次我们要选择边权改变后,可以改变的贡献值最大的那条边,也就是上述计算出来的值最大的
利用优先队列可以容易得到
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 1e5 + 5;
struct node{
ll w, cnt;
bool operator < (const node &_) const {
return (w-w/2)*cnt < (_.w-_.w/2)*_.cnt;
}
};
vector<pair<int, int> > ed[maxn];
int num[maxn];
ll w[maxn];
int dfs(int u, int fa) {
if(u != 1 && (int)ed[u].size() == 1) return 1;
int ans = 0;
for(auto i : ed[u]) {
if(i.first == fa) continue;
num[i.second] = dfs(i.first, u);
ans += num[i.second];
}
return ans;
}
void solve(int n, ll S) {
priority_queue<node> q;
while(!q.empty()) q.pop();
ll ans = 0;
for(int i = 1; i < n; ++i) {
ans += w[i] * 1ll * num[i];
q.push(node{w[i], num[i] * 1ll});
}
ll x, ww, cnt; int res = 0;
while(ans > S) {
ww = q.top().w, cnt = q.top().cnt; q.pop();
x = ww / 2;
ans = ans - (ww - x) * cnt;
res += 1;
q.push(node{x, cnt});
}
printf("%d\n", res);
}
int main(){
int T;
scanf("%d", &T);
while(T--) {
int n; ll S;
scanf("%d%lld", &n, &S);
for(int i = 1; i <= n; ++i) ed[i].clear();
for(int i = 1, u, v; i < n; ++i) {
scanf("%d%d%lld", &u, &v, &w[i]);
ed[u].push_back(make_pair(v, i));
ed[v].push_back(make_pair(u, i));
}
dfs(1, 1);
solve(n, S);
}
return 0;
}
/*
4 10
1 2 2
2 3 3
2 4 4
*/
E2 - Weights Division (hard version) [优先队列][二分] ★★★
参考博客
Codeforces1399 E2. Weights Division (hard version)(堆+前缀和+二分)
题意
给一个有根树, 1 1 1 为根节点,叶子节点就是没有子节点的点
每条边 ( u , v ) (u, v) (u,v) 有一个权值,每次可以选择任意一条边,使这条边的权值变为 ⌊ w i 2 ⌋ \lfloor \frac{w_i}{2} \rfloor ⌊2wi⌋,每次选择这条边的花费为 c [ i ] c[i] c[i]
要使 1 1 1 到所有叶子节点的路径和 ≤ S \leq S ≤S,问总花费最少为多少
分析
原本想延续 E1 的做法直接暴力优先队列,然后超时了
对于花费分为两类 1 , 2 1, 2 1,2,那么可以考虑将这两个不同的花费分别放在不同的优先队列维护
计算出操作次数 i i i,边权的最大的减少量 s u m [ i ] sum[i] sum[i]
最坏情况,操作次数最大也就为 1 0 5 ⋅ l o g 2 1 0 6 10^5 · log_210^6 105⋅log2106,且这样的情况下另一种花费的操作次数为 0 0 0
然后再枚举花费 1 1 1 的操作次数,二分花费 2 2 2 的操作次数,找到满足条件的最小操作次数
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 1e18;
const int inf = 0x3f3f3f3f;
const int maxn = 1e5 + 5;
struct node{
ll w, cnt;
bool operator < (const node &_) const {
return (w-w/2)*cnt < (_.w-_.w/2)*_.cnt;
}
};
vector<pair<int, int> > ed[maxn]; // 记录边,求边次数
priority_queue<node> q[2]; // 0,1 为 1,2 花费的边
vector<ll> sum[2]; // 0,1 为 1,2 花费的边 前缀和
ll w[maxn]; // 边权
int len[2]; // 前缀和长度
int num[maxn]; // 边次数
int c[maxn]; // 边花费
int dfs(int u, int fa) {
if(u != 1 && (int)ed[u].size() == 1) return 1;
int ans = 0;
for(auto i : ed[u]) {
if(i.first == fa) continue;
num[i.second] = dfs(i.first, u);
ans += num[i.second];
}
return ans;
}
void get(int n, ll ans, ll S, int k) {
ll x, ww, cnt;
sum[k].push_back(0);
while(!q[k].empty()) {
ww = q[k].top().w, cnt = q[k].top().cnt; q[k].pop();
sum[k].push_back((ww - ww/2) * cnt + sum[k][len[k]]);
len[k] += 1;
if(ans - sum[k][len[k]] <= S) break;
if(ww/2 != 0) q[k].push(node{ww/2, cnt});
}
}
inline void init(int n) {
for(int i = 0; i < 2; ++i) {
len[i] = 0; sum[i].clear();
while(!q[i].empty()) q[i].pop();
}
for(int i = 1; i <= n; ++i) ed[i].clear();
}
int solve(int n, ll ans, ll S) {
get(n, ans, S, 0);
get(n, ans, S, 1);
int ans2 = inf;
for(int i = 0; i <= len[0]; ++i) { // 枚举+二分
ll tmp = ans - sum[0][i];
int l = 0, r = len[1], pos = inf;
while(l <= r) {
int mid = (l + r) >> 1;
if(tmp - sum[1][mid] <= S)
pos = mid, r = mid-1;
else
l = mid+1;
}
ans2 = min(ans2, i+pos*2);
}
return ans2;
}
int main(){
int T;
scanf("%d", &T);
while(T--) {
int n; ll S;
scanf("%d%lld", &n, &S);
init(n);
for(int i = 1, u, v; i < n; ++i) {
scanf("%d%d%lld%d", &u, &v, &w[i], &c[i]);
ed[u].push_back(make_pair(v, i));
ed[v].push_back(make_pair(u, i));
}
dfs(1, 1);
ll ans = 0; // 最初的路径总和
for(int i = 1; i < n; ++i) {
ans += w[i] * 1ll * num[i];
if(c[i] == 1) q[0].push(node{w[i], num[i]*1ll});
else q[1].push(node{w[i], num[i]*1ll});
}
printf("%d\n", ans <= S ? 0 : solve(n, ans, S));
}
return 0;
}