Codeforces Round 1023 (Div. 2)(ABCDF1)

A. LRC and VIP

翻译:

        您有一个大小为 n 的数组 a —— a_1,a_2,...,a_n

        您需要将 n 元素分成 2 个 序列 B 和 C满足以下条件:

  • 每个元素只属于一个序列。
  • 两个序列 B 和 C 至少包含一个元素。
  • gcd(B_1,B_2,...,B_{|B|})]\neq gcd(C_1,C_2,...,C_{|C|})

思路:

        gcd的值必然会越来越小,那么可以将a的最大值单独一类,其余一类。

实现:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MX = 2e5+10;

void solve(){
    int n;cin>>n;
    vector<int>a(n),b(n);
    for (auto& i:a)cin>>i;
    for (int i=0;i<n;i++) b[i] = i;
    sort(b.begin(),b.end(),[&](int x,int y){
        return a[x]<a[y];
    });
    if (a[b[0]]!=a[b[n-1]]){
        cout<<"Yes"<<endl;
        vector<int> ans(n,1);
        ans[b[n-1]] = 2;
        for (int i=0;i<n;i++) cout<<ans[i]<<" ";cout<<endl;
    }else{
        cout<<"No"<<endl;
    }
}

int main(){
    // 关闭输入输出流同步
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    // 不使用科学计数法
    // cout<<fixed;
    // 四舍五入中间填保留几位小数,不填默认
    // cout.precision();
    int t;
    cin>>t;
    while (t--){
        solve();
    }
    return 0;
}



B. Apples in Boxes

翻译:

        汤姆和杰瑞在地下室找到了一些苹果。他们决定玩一个游戏来获得一些苹果。

        一共有 n 个盒子,第 i 个盒子里有 a_i 个苹果。汤姆和杰瑞轮流捡苹果。汤姆先捡。轮到他们时,他们必须做以下事情:

  • 选择第 i 个(1≤i≤n)装有正数苹果的盒子,即 a_i>0,然后从这个盒子里摘 1 个苹果。注意,这样会将 a_i 减少 1。
  • 如果没有有效的盒子,当前棋手就输了。
  • 如果移动后 max(a_1,a_2,...,a_n)-min(a_1,a_2,...,a_n)>k 成立,那么当前棋手(下最后一步棋的棋手)也会输。

        如果双方都下得很好,请预测对局的胜负。

思路:

        最佳策略必然是取有最大值的盒子,因为能最大可能避免第三种情况。那么除了一拿就输的情况,只有拿完的情况。分类讨论即可。

实现:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MX = 2e5+10;

void solve(){
    ll n,k;
    cin>>n>>k;
    ll maxx = 0,max_cnt = 0,minn = LLONG_MAX,summ = 0;
    for (ll num,i=1;i<=n;i++){
        cin>>num;summ+=num;
        if (maxx<num){
            maxx = num;
            max_cnt = 1;
        }else if (maxx==num) max_cnt++;
        minn = min(num,minn);
    }
    ll now = (max_cnt>=2 ? maxx : maxx-1);
    if (now-minn>k){
        cout<<"Jerry"<<endl;
    }else{
        if (summ%2==0) cout<<"Jerry"<<endl;
        else cout<<"Tom"<<endl;
    }
}

int main(){
    // 关闭输入输出流同步
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    // 不使用科学计数法
    // cout<<fixed;
    // 四舍五入中间填保留几位小数,不填默认
    // cout.precision();
    ll t;
    cin>>t;
    while (t--){
        solve();
    }
    return 0;
}



C. Maximum Subarray Sum

翻译:

        给你一个长度为 n 的数组 a_1,a_2,...,a_n 和一个正整数 k,但是数组 a 的某些部分缺失了。你的任务是补全缺失的部分,使 a 的最大子数组和∗ 恰好为 k,否则就报告不存在解法。

        在形式上,我们给你一个二进制字符串 s 和一个部分填充的数组 a,其中:

  • 如果你记得 a_i 的值,则 s_i=1 表示该值,然后给你 a_i的实际值。
  • 如果你不记得 a_i 的值,则 s_i=0 表示该值,并给出 a_i=0

        你记住的所有值都满足 |a_i|\leq 10^6 的条件。但是,您可以使用最大 10^{18} 的值 来填充你不记得的值。可以证明,如果存在一个解,那么也存在一个满足 |a_i|\leq 10^{18} 的解。
.

思路:

        在第一个不确定点出现的地方,令该点的值为k减其自左右点出发的最大和。其余不确定点都赋值为-10^{12},以避免可能导致的最大和变化。(构造)

实现:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MX = -2e12;

void solve(){
    ll n,k;cin>>n>>k;
    string s;cin>>s;
    s = ' '+s;
    vector<ll> a(n+1);
    for (ll i=1;i<=n;i++) cin>>a[i];
    for (ll f=0,i=1;i<=n;i++){
        if (s[i]=='0'){
            if (f) {a[i] = MX;continue;}
            ll lm = 0,lmm = -1;
            for (ll j=i-1;j>=1;j--){
                if (s[j]=='0') break;
                lm+=a[j];
                lmm = max(lm,lmm);
            }
            ll rm = 0,rmm = -1;
            for (ll j=i+1;j<=n;j++){
                if (s[j]=='0') break;
                rm+=a[j];
                rmm = max(rm,rmm);
            }
            lmm = max(0ll,lmm);
            rmm = max(0ll,rmm);
            a[i] = k-lmm-rmm;
            f = 1;
        }
    }
    ll tmp=0,maxx =0,f=0;
    for (int i=1;i<=n;i++){
        tmp=max(0ll,a[i]+tmp);
        maxx = max(maxx,tmp);
        if (maxx>k) {
            cout<<"No"<<endl;
            return;
        }else if (maxx==k) f = 1;
    }
    if (!f){
        cout<<"No"<<endl;
        return;
    }
    cout<<"Yes"<<endl;
    for (int i=1;i<=n;i++){
        cout<<a[i]<<" ";
    }cout<<endl;
}

int main(){
    // 关闭输入输出流同步
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    // 不使用科学计数法
    // cout<<fixed;
    // 四舍五入中间填保留几位小数,不填默认
    // cout.precision();
    ll t;
    cin>>t;
    while (t--){
        solve();
    }
    return 0;
}

D. Apple Tree Traversing

翻译:

        有一棵有 n 个节点的苹果树,最初每个节点都有一个苹果。你随身带着一张纸,最初上面什么也没写。

        只要苹果树上至少还有一个苹果,你就在苹果树上进行以下操作:

  • 选择一条苹果路径 (u,v)。当且仅当路径 (u,v) 上的每个节点上都有一个苹果时,路径 (u,v) 才称为苹果路径。
  • 假设 d 是路径上苹果的个数,在纸上依次写下三个数字(d,u,v)。
  • 然后把路径(u,v)上的苹果全部移走。

        这里的路径 (u,v) 是指从 u 到 v 的唯一最短路径上的顶点序列。

        让纸上的数字序列为 a。你的任务是找出可能的最大字典序的序列 a。

思路:

        要最大字典序,要优先苹果的数量,再是端点的值。因此先求出树中的最长路径,同时对端点进行比较,将答案放入ans数组,在对剩下来的森林进行上述相同操作,直到所有点都被取完。最后对ans进行排序得到答案。(dfs,贪心)

        如何求树的最长路径(也称为直径)。先在树上随便找个点x,通过x进行dfs得到距离最远的u。在对u进行dfs得到v,u->v就为直径。

        为什么两个dfs可得到直径?考虑三种情况:

        1. 如果 x 在 u->v的路径上,那么x到的最远点必为u/v,那么再次dfs得到的必为u到v的路径。

        2. 如果 x 不在 u->v的路径上,在 x 找最远点时经过u到v路径上的点y,从y点再次出发如果得到的最远点不为u/v。说明有更远的距离比y->u或y->v那就与u->v最远不符合。那么y必定会到u/v。

        3.如果 x 不在 u->v的路径上,在 x 找最远点时不经过u到v路径上的点,由于是一棵树这两线必定有相联系的点,说明从联系点出发有更远的路径,就与u->v最远不符合。那么y必定会到u/v。

        最多进行\sqrt n轮次的全面取值,每一轮都会对森林进行取直径的操作,那么最坏就是每轮只会取一棵树值从k到1,那么可得到轮数小于根号n。

        时间复杂度为:O(n\sqrt n)

实现:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MX = -2e12;
// 得到树的直径和端点的最大情况
// 进行sqrt(n)轮,每次找直径为n
int n;
void solve(){
    cin>>n;
    vector<vector<int>> tree(n+1);
    vector<array<int,3>> ans;
    // 判断点有无被取
    vector<int> vis(n+1,0);
    // 记录路径
    vector<int> pre_path(n+1,-1);
    for (int u,v,i=1;i<n;i++){
        cin>>u>>v;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }

    // dis,to
    auto dfs = [&](auto&& dfs,int u,int father=-1)->pair<int,int>{
        pair<int,int> tmp = {1,u};
        pre_path[u] = father;
        for (int v:tree[u]){
            if (v==father || vis[v]) continue;
            auto nex = dfs(dfs,v,u);
            nex.first+=1;
            tmp = max(tmp,nex);
        }
        return tmp;
    };
    while (1){
        if (count(vis.begin()+1,vis.end(),0)==0) break;
        // 得到树的直径
        for (int i=1;i<=n;i++){
            if (vis[i]) continue;
            auto [_,u] = dfs(dfs,i);
            auto [d,v] = dfs(dfs,u);
            ans.push_back({d,max(u,v),min(u,v)});
            while (v!=-1){
                vis[v] = 1;
                v = pre_path[v];
            }
        }
    }
    sort(ans.begin(),ans.end(),greater<array<int,3>>());
    for (auto [d,u,v]:ans){
        cout<<d<<" "<<u<<" "<<v<<" ";
    }cout<<endl;
}

int main(){
    // 关闭输入输出流同步
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    // 不使用科学计数法
    // cout<<fixed;
    // 四舍五入中间填保留几位小数,不填默认
    // cout.precision();
    ll t;
    cin>>t;
    while (t--){
        solve();
    }
    return 0;
}

F1. Cycling (Easy Version)

翻译:

        Leo 在市中心做程序员,他的爱人在郊区的一所高中教书。每个周末,里奥都会骑着自行车去郊区,和爱人一起度过一个愉快的周末。

        现在这条路上有 N 个骑自行车的人在 Leo 前面。他们的编号从前到后依次为 1、2、......、n。最初,李奥在第 n 个骑车人后面。第 i 位骑车人的敏捷值为 ai。

        李奥想超过第 1 位骑车人。里欧可以多次采取以下行动:

  • 假设李奥前面的第一个人是第 i 位骑车人,他可以以 ai 的代价走在第 i 位骑车人前面。这将使他位于骑自行车者 i-1 的后面。
  • 利用他的超能力,将 ai 和 aj 互换(1≤i<j≤n),费用为 (j-i)。

        利奥想知道在第 1 个骑车人前面的最小成本。这里只需打印整个数组的答案,即数组 [a1,a2,...,an]。

思路:

       dp[i]:为a[i:n]的最小花费。

        对于一个数组中最小的第一个数 x 之前的,通过交换最小速度在超车比直接超车速度快,在x之后的部分,考虑将x向后移动再移回来的操作加后面的区间的最小值。后面区间的左端点的值无论如何都不可能比 x 要小,因此这个区间不管怎么操作都不会影响向前的区间,满足了无后效性。(线性dp)

实现:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// dp[i]:a[i:n] minna cost
void solve(){
    ll n;
    cin>>n;
    vector<ll> a(n+1),dp(n+2,LLONG_MAX);
    dp[n+1] = 0;
    for (ll i=1;i<=n;i++) cin>>a[i];
    for (ll i=n;i>=1;i--){
        // first minna position
        ll p = i;
        for (ll j=i;j<=n;j++){
            if (a[j]<a[p]) p = j;
        }
        for (ll j=p;j<=n;j++){
            dp[i] = min(dp[i],a[p]*(j-i+1)+(j-p)+(j-i)+dp[j+1]);
        }
    }
    cout<<dp[1]<<endl;
}

int main(){
    // 关闭输入输出流同步
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    // 不使用科学计数法
    // cout<<fixed;
    // 四舍五入中间填保留几位小数,不填默认
    // cout.precision();
    ll t;
    cin>>t;
    while (t--){
        solve();
    }
    return 0;
}

之后补题会在此增加题解。    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cando-01

随心而动,随性而为。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值