Educational Codeforces Round 164 A-E

文章探讨了几个编程问题,涉及最优分组、贪心选择策略(如保证前缀相等最大化乘积)、背包问题的应用以及利用动态规划解决最大贡献问题。这些问题主要集中在IT技术中的算法设计与分析上。
摘要由CSDN通过智能技术生成

A
均匀分组是最优的
n − ⌈ n m ⌉ > k n-\lceil {n \over m} \rceil>k nmn>k输出 Y e s Yes Yes

// LUOGU_RID: 155897482
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
void paralysis(){
    int n,m,k;
    cin>>n>>m>>k;
    if (n-ceil(1.0*n/m)<=k){
        cout<<"NO\n";
    }else{
        cout<<"YES\n";
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int T=1;
    cin>>T;
    while (T--){
        paralysis();
    }
    return 0;
}

B
a 1 a_1 a1即为美丽数组最后的数,只要删去 a a a中最小的 a 1 a_1 a1连通块

// LUOGU_RID: 155897774
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
void paralysis(){
    int n;
    cin>>n;
    vector<int> a(n+1);
    for (int i=1;i<=n;i++){
        cin>>a[i];
    }

    int sum=0,ans=1e9,flag=0;
    for (int i=1;i<=n;i++){
        if (a[i]==a[1]){
            sum++;
        }else{
            flag=1;
            ans=min(ans,sum);
            sum=0;
        }
    }
    if (sum&&flag){
        ans=min(ans,sum);
    }
    cout<<(ans==1e9?-1:ans)<<"\n";
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int T=1;
    cin>>T;
    while (T--){
        paralysis();
    }
    return 0;
}

C
分两个序列
发现 s 1 + s 2 s1+s2 s1+s2操作后始终为定值,两者差越小乘积越大,所以贪心:
前缀相等,大数放 s 1 s1 s1,小数放 s 2 s2 s2
前缀不相等,反过来

// LUOGU_RID: 155977710
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
void paralysis(){
    string s1,s2;
    cin>>s1>>s2;
    bool flag=0;
    for (int i=0;i<s1.size();i++){
        if (!flag){
            if (s2[i]>s1[i]){
                swap(s1[i],s2[i]);
                flag=1;
            }else if (s1[i]>s2[i]){
                flag=1;
            }
        }else{
            if (s1[i]>s2[i]){
                swap(s1[i],s2[i]);
            }
        }
    }
    cout<<s1<<"\n"<<s2<<"\n";
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int T=1;
    cin>>T;
    while (T--){
        paralysis();
    }
    return 0;
}

D
首先对于一个序列 a a a ,最小分组是一个经典问题
对于 m a x ( a ) > = s u m ( a ) − m a x ( a ) max(a)>=sum(a)-max(a) max(a)>=sum(a)max(a),分 m a x ( a ) max(a) max(a)
否则分 ⌈ s u m ( a ) 2 ⌉ \lceil {sum(a) \over 2} \rceil 2sum(a)

再回到原问题, a i a_i ai 选与不选是一个背包问题,再由题目所说球总数不超过5000,所以 f i , j f_{i,j} fi,j统计个数,那么每次答案只会在选 a i a_i ai时产生,发现 s u m sum sum j j j 中表示了, m a x max max 可以对 a a a 排序后再dp快速查询

// LUOGU_RID: 155902288
#include <bits/stdc++.h>
#define ll long long
constexpr int mod=998244353;
using namespace std;
void paralysis(){
    int n;
    cin>>n;
    vector<int> a(n+1);
    int sum=0;
    for (int i=1;i<=n;i++){
        cin>>a[i];
        sum+=a[i];
    }
    sort(a.begin()+1,a.end());

    vector<vector<int>> f(n+1,vector<int>(sum+1));

    f[0][0]=1;
    ll ans=0;
    for (int i=1;i<=n;i++){
        for (int j=a[i];j<=sum;j++){
            f[i][j]=f[i-1][j-a[i]];
            if (a[i]>j-a[i]){
                ans=(ans+1LL*f[i][j]*a[i]%mod)%mod;
            }else{
                ans=(ans+1LL*f[i][j]*((j+1)/2)%mod)%mod;
            }
        }
        for (int j=0;j<=sum;j++){
            f[i][j]=(f[i][j]+f[i-1][j])%mod;
        }
    }
    cout<<ans<<"\n";

}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int T=1;
    // cin>>T;
    while (T--){
        paralysis();
    }
    return 0;
}

E
首先对于 k = 1 k=1 k=1 时,它是一个经典问题 [NOIP2018 提高组] 铺设道路, 遍历 a i a_i ai,当 a i > a i − 1 a_i>a_{i-1} ai>ai1时才产生贡献。然后就很好求了

对于 k > 1 k>1 k>1的情况,处理一下 a i = ⌈ a i k ⌉ a_i={\lceil {a_i \over k} \rceil} ai=kai,转换为经典问题,暴力做是 O ( n a ) O(na) O(na)

方法一:
对于除法操作,我们想是否可以数论分块,优先枚举 i i i ,然后枚举 k k k ,转换一下向上取整 ⌈ a i k ⌉ = ⌊ a i + k − 1 k ⌋ {\lceil {a_i \over k} \rceil}={\lfloor {{a_i+k-1} \over k} \rfloor} kai=kai+k1 ,然后就可以了

// LUOGU_RID: 155941020
#include <bits/stdc++.h>
#define int long long
using namespace std;
void paralysis(){
    int n;
    cin>>n;
    vector<int> a(n+1);
    int maxn=0;
    for (int i=1;i<=n;i++){
        cin>>a[i];
        maxn=max(maxn,a[i]);
    }

    vector<int> ans(maxn+2);
    for (int i=1;i<n;i++){
        int use1=a[i]-1,use2=a[i+1]-1;
        for (int l=1,r=1;l<=maxn;l=r+1){
            r=min(use1/l?use1/(use1/l):maxn,use2/l?use2/(use2/l):maxn);
            int x=use1/l+1,y=use2/l+1;
            if (y>x){
                ans[l]+=y-x;
                ans[r+1]-=y-x;
            }
        }
    }

    for (int i=1;i<=maxn;i++){
        ans[i]+=ans[i-1];
        cout<<ans[i]+(a[1]+i-1)/i<<" ";
    }
    cout<<"\n";
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int T=1;
    // cin>>T;
    while (T--){
        paralysis();
    }
    return 0;
}

方法二:
发现本质还是一个求贡献的式子 ∑ a i > a i − 1 ⌈ a i k ⌉ − ⌈ a i − 1 k ⌉ \sum_{a_i>a_{i-1}} {\lceil{a_i \over k} \rceil} -{\lceil{a_{i-1} \over k} \rceil} ai>ai1kaikai1
对于枚举 k k k 后,其实可以看作枚举了因数,我们可以类似用埃氏筛的方法,再枚举 ⌈ a i k ⌉ {\lceil{a_i \over k} \rceil} kai,这样计算出 a i a_i ai的范围,前缀和预处理一下 a i a_i ai 的系数即可,因为是调和级数, O ( a l o g a ) O(aloga) O(aloga)

#include <bits/stdc++.h>
#define int long long
using namespace std;
void paralysis(){
    int n;
    cin>>n;
    vector<int> a(n+1);
    int maxn=0;
    for (int i=1;i<=n;i++){
        cin>>a[i];
        maxn=max(maxn,a[i]);
    }

    vector<int> sum(maxn+1);
    sum[a[1]]++;
    for (int i=1;i<n;i++){
        if (a[i+1]>a[i]){
            sum[a[i+1]]++;
            sum[a[i]]--;
        }
    }
    for (int i=1;i<=maxn;i++){
        sum[i]+=sum[i-1];
    }

    for (int k=1;k<=maxn;k++){
        int ans=0;
        for (int j=1;j<=(maxn+k-1)/k;j++){
            ans+=j*(sum[min(maxn,j*k)]-sum[(j-1)*k]);
        }
        cout<<ans<<" \n"[k==maxn];
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int T=1;
    // cin>>T;
    while (T--){
        paralysis();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值