2024.12.2 Educational Codeforces Round 172

Solved: 4/6

Upsolved: 5/6

Rank: 205


脑洞题做多了导致套路题不会做了。。。


A. Greedy Monocarp

题意:给一个序列,每次可以给其中一个数加1,问最少操作多少次可以满足当从大到小取数时取到的数之和会恰好等于 \(k\)

从大到小排序,设恰好小于 \(k\) 的前缀和为 \(s\),答案就是 \(k-s\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()

void solve(){
    int n,k;
    cin>>n>>k;
    vector<int> a(n);
    ll sum=0,ans=-1;
    for(int& x:a)cin>>x,sum+=x;
    sort(all(a));
    for(int i=0;i<n;++i){
        if(sum<=k){ans=k-sum;break;}
        sum-=a[i];
    }
    cout<<ans<<'\n';
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);
    int T;
    cin>>T;
    while(T--)solve();
}

B. Game with Colored Marbles

题意:有一个序列,两人轮流取数。先手每取到一个值都会得 1 分,如果取到某个值的所有数又会额外得 1 分。先手希望得分最多,后手希望得分最少。问最优策略下的分数。

最优策略下,一定是先取完所有出现次数为 1 的数(先手会取得 \(\lceil \frac{c}2\rceil\) 个),再取剩下的(先手每个数都会取到但都不会取完)。答案就是出现的值的数量加(出现次数为 1 的数的数量 mod 2)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()

void solve(){
    int n,x;
    cin>>n;
    vector<int> c(n+1);
    for(int i=0;i<n;++i)cin>>x,++c[x];
    int cnt1=0,cnt2=0;
    for(int i=1;i<=n;++i){
        if(c[i]==1)++cnt1;
        else if(c[i]>1)++cnt2;
    }
    cout<<cnt1+(cnt1&1)+cnt2<<'\n';
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);
    int T;
    cin>>T;
    while(T--)solve();
}

C. Competitive Fishing

题意:有一个 \(\pm 1\) 序列,给每个位置赋一个连续且单调不降的自然数权值(即 00111223333 这样的),使得所有数字与权值的乘积之和至少为 \(k\),求最大权值的最小值。

每个权值增加的位置对答案的贡献是从这个位置开始的后缀和。因此给所有后缀和排序,从大到小加直到超过 \(k\) 为止即可(注意第一个位置不能加)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()

void solve(){
    int n,k;
    cin>>n>>k;
    string a;
    cin>>a;
    vector<int> s(n);
    for(int i=n-1;i;--i)s[i-1]=s[i]+(a[i]=='1'?1:-1);
    s.pop_back();
    sort(all(s));
    int m=1,sum=0;
    for(int i=n-2;~i;--i){
        if(sum>=k)break;
        sum+=s[i],++m;
    }
    if(sum<k)cout<<"-1\n";
    else cout<<m<<'\n';
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);
    int T;
    cin>>T;
    while(T--)solve();
}

upd: 被叉了。叉点:2e5 个 0。死因:sum 爆 int


D. Recommendations

题意:有 \(n\) 个区间。对每个区间,求所有覆盖它的区间的交的长度减去它本身的长度。

对所有区间按左端点从小到大排序,用set维护右端点,枚举到一个区间时其右端点的lower_bound就是覆盖区间交的右端点。反过来做一遍即可得到区间交的左端点。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define all(x) (x).begin(),(x).end()

struct node{int l,r,id;};

void solve(){
    int n;
    cin>>n;
    vector<node> a(n);
    for(int i=0;i<n;++i)cin>>a[i].l>>a[i].r,a[i].id=i;
    sort(all(a),[=](node a,node b){return a.l<b.l||a.l==b.l&&a.r>b.r;});
    set<int> s;
    vector<int> l(n),r(n);
    for(int i=0;i<n;++i){
        auto it=s.lower_bound(a[i].r);
        if(it==s.end())r[a[i].id]=-1;
        else r[a[i].id]=*it;
        if(i<n-1&&a[i].l==a[i+1].l&&a[i].r==a[i+1].r)r[a[i].id]=-1;
        s.insert(a[i].r);
    }
    s.clear();
    sort(all(a),[=](node a,node b){return a.r>b.r||a.r==b.r&&a.l<b.l;});
    for(int i=0;i<n;++i){
        auto it=s.upper_bound(a[i].l);
        if(it==s.begin())l[a[i].id]=-1;
        else l[a[i].id]=*(--it);
        if(i<n-1&&a[i].l==a[i+1].l&&a[i].r==a[i+1].r)l[a[i].id]=-1;
        s.insert(a[i].l);
    }
    sort(all(a),[=](node a,node b){return a.id<b.id;});
    for(int i=0;i<n;++i){
        if(l[i]==-1||r[i]==-1)cout<<"0\n";
        else cout<<(r[i]-l[i])-(a[i].r-a[i].l)<<'\n';
    }
}
int main(){
    ios::sync_with_stdio(0);cin.tie(0);
    int T;
    cin>>T;
    while(T--)solve();
}

E. Vertex Pairs

题意:给一棵 \(2n\) 个节点的树,每个节点的权值为 \(1\sim n\) 且每个值恰好出现 \(2\) 次。编号为 \(i\) 的节点的费用为 \(2^i\),求费用最小的包含所有权值的连通集合。

首先可以注意到:因为集合的大小至少为 \(n\),超过总点数的一半,所以这个集合一定至少包含一个重心。

以重心为根(如果有两个重心要分别做一遍)。从大到小枚举节点看是否可以删除。因为答案集合一定包含重心,所以要删除一个点只能删除以它为根的整棵子树。

考虑如何快速判断能否删除。dfs序将子树转化为区间,然后用树状数组维护每个位置的值是否能被删除:

  • 每个值的两个点的lca一定不能被删除;

  • 若一个值的两个点有一个点被删了,那么另一个点不能再被删。

删除过程中维护树状数组,判断时判区间和大于0即可。


posted @ 2024-12-03 01:15  EssnSlaryt  阅读(129)  评论(0编辑  收藏  举报