[Codeforces Round #661 (Div. 3)]1399




竞赛首页 Codeforces Round #661 (Div. 3)

A - Remove Smallest

题意

对于 ∣ a i − a j ∣ ≤ 1 ( i ≠ j ) | a_i - a_j| \leq 1(i \not= j) aiaj1(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

题意

每次可以对数组进行如下操作

  1. a i − 1 a_i - 1 ai1
  2. b i − 1 b_i - 1 bi1
  3. a i − 1 , b i − 1 a_i - 1, b_i - 1 ai1,bi1

问至少需要多少次操作,才可以使所有 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=sai
直接枚举 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 (wi2wi)×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 105log2106,且这样的情况下另一种花费的操作次数为 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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值