【Codeforces Round #521 (Div. 3)】A.B.C.D.E.F1.F2

本文深入解析了一场Codeforces竞赛中的六道题目,涵盖FrogJumping、DisturbedPeople、GoodArray等,提供了详细的解题思路与代码实现,适用于算法竞赛选手及编程爱好者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

不算分的场可能心态好一些五个1A不算分的场可能心态好一些五个1A1A
但是最后的F知道是dp却优化不出来,但是最后的F知道是dp却优化不出来,Fdp
最终rank254.放到div2又要掉分了。最终rank254.放到div2又要掉分了。rank254.div2O(∩_∩)O


A. Frog Jumping

题意

一只小青蛙从原点跳k次,第奇数次向右跳a,偶数次向左跳b,问最后停在哪

做法

算出两次跳跃的长度,再用个除法,最后判一下有没有最后一跳就可以了。

代码

#include<stdio.h>
#include<iostream>
using namespace std;
typedef long long ll;
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        ll a,b,k;
        scanf("%lld%lld%lld",&a,&b,&k);
        ll ans=0;
        ans=(a-b)*(k/2);
        if(k%2==1) ans=ans+a;
        printf("%lld\n",ans);
    }
    return 0;
}


B. Disturbed People

题意

一个长度为n的01序列,某个位置被定义为被打扰的原则是下标为1&lt;i&lt;n1&lt;i&lt;n1<i<n
而且ai=0,ai−1=1,ai+1=1a_i=0,a_{i-1}=1,a_{i+1}=1ai=0,ai1=1,ai+1=1,可以对010101序列的每个位置修改,
求最少修改次数使整个序列没有被打扰的位置

做法

从前往后找到101101101,统计答案并向后跳三个继续找就可以了。
因为如果有101011010110101这种,我们修改第一个遇到的之后向后跳三个也是最优的。

代码

#include<stdio.h>
const int maxn = 1e5+5;
int a[maxn];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int ans=0;
    for(int i=1;i<=n-2;i++)
    {
        if(a[i]==1&&a[i+1]==0&&a[i+2]==1)
        {
            i+=2;
            ans++;
        }
    }
    printf("%d\n",ans);
    return 0;
}

C. Good Array

题意

给你一个长度为2∗1052*10^52105的序列
一个序列被定义为好的原则是序列中有某个元素等于其余元素之和。
问是否可以删除一个数让这个序列变为好序列,如果有输出这些位置

做法

因为我们想一下如果一个数等于其他数之和,他一定是最大的那个
所以我们只要将原序列排序,看剩下的数的和是不是等于最大数的两倍就可以

坑点

要注意最大值本身被删除时,应该看剩下数之和是不是次大值的两倍。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
ll a[maxn];
struct data
{
    ll num;
    int id;
}x[maxn];
bool cmp(const data &a,const data &b)
{
    return a.num<b.num;
}
vector<int> ans;
int main()
{
    int n;
    scanf("%d",&n);
    ll sum=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&x[i].num);
        x[i].id=i;
    }
    sort(x+1,x+1+n,cmp);
    for(int i=1;i<=n;i++)
    {
        sum+=x[i].num;
    }
    for(int i=1;i<=n;i++)
    {
        ll tmp=sum-x[i].num;
        if(i==n)
        {
            if(tmp==x[n-1].num*2LL) ans.push_back(x[i].id);//最大值本身被删除的情况
        }
        else if(tmp==x[n].num*2LL)
        {
            ans.push_back(x[i].id);//最大值还在的情况
        }
    }
    printf("%d\n",ans.size());
    for(int i=0;i<ans.size();i++)
    {
        printf("%d ",ans[i]);
    }
    return 0;
}


D. Cutting Out

题意

给你一个长度为n序列,现在要你选出一个长度为k的子序列,
每次在原序列中拿出这个子序列,使可以拿的次数最多,输出这个子序列
1&lt;=k&lt;=n&lt;=2∗1051&lt;=k&lt;=n&lt;=2*10^51<=k<=n<=2105

做法

看到这个数据范围我们首先就应该想到是不是可以二分
发现次数是具有二分性质的(也就是能拿n次的序列一定能拿n-1次)
于是我们二分这个最终次数,
这样每个数字在选出的子序列中最多可以出现几次也就可以计算出来
然后看所有数字在选出的子序列中可以出现的总次数是否大于k进行check

坑点

注意输出的时候最后一种数字不需要全输出,凑够k个数字就可以。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 2e5+5;
int a[maxn];
int num[maxn];
struct data
{
    int num;
    int id;
}x[maxn];
int n,k;
bool cmp(const data &a,const data &b)
{
    return a.num>b.num;
}
bool check(int mid)
{
    int ans=0;
    for(int i=1;i<=k;i++)
    {
        ans+=x[i].num/mid;//得到每种数字可以在最终序列的出现次数
        if(ans>=k) return true;
    }
    return false;
}
void print(int mid)
{
    int ans=0;
    for(int i=1;i<=k;i++)
    {
        if(ans+x[i].num/mid>=k)//注意这里,只要凑够k个数字即可
        {
           for(int j=1;j<=k-ans;j++)  printf("%d ",x[i].id);
            return ;
        }
        else
        {
            ans+=x[i].num/mid;
            for(int j=1;j<=x[i].num/mid;j++)  printf("%d ",x[i].id);
        }
    }
    return ;
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) num[a[i]]++;
    for(int i=1;i<=200000;i++)
    {
        x[i].id=i;
        x[i].num=num[i];
    }
    sort(x+1,x+1+200000,cmp);
    int l=1,r=200000,mid;//二分最终答案的次数
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(check(mid)) l=mid+1;
        else r=mid-1;
    }
    print(r);
    return 0;
}


E. Thematic Contests

题意
现在有n个题目,每种题目有自己的类型,1&lt;=n&lt;=2∗1051&lt;=n&lt;=2*10^{5}1<=n<=2105
要举办一次考试,
考试的原则是
每天只有一种题目类型
一种题目类型只能出现在一天
每天的题目类型不能相同,
而且后一天的题目数量必须正好为前一天的两倍,第一天的题目数量是任意的
求怎么安排能使题目尽量多。注意:不是天数尽量多

做法
首先我们知道一件事,题目属于哪种类型并不重要,重要的是每种类型的个数
所以我们先统计出所有类型的个数,存进一个数组,
而最终最优解一定是按照题目数量从小到大来的,所以我们对该数组进行排序
而我们怎么确定最终怎么选择呢,我们发现最终怎么选择取决于第一天怎么选择
而且只要确定了第一天的题目数,只要在排序好的数组上采取能选就选的原则,
就可以O(n2)O(n^2)O(n2)的时间复杂度解决这道题
可是这个复杂度并不能通过,所以我们要看哪个过程可以优化
第一天的题目数并不满足二分的性质,我们就考虑优化能选就选这个原则
在排好序的数组上我们可以通过lower_bound快速找到>=当前所需数目的下标pos
之后让pos++,继续下一次查找,这样我们最多查找log次就能完成check
于是这道题就是O(n)O(n)O(n)枚举第一天的题目数,O(logn)O(logn)O(logn)check,总复杂度O(nlogn)O(nlogn)O(nlogn)

坑点

check的时候所需题目数&gt;=2∗105check的时候所需题目数&gt;=2*10^5check>=2105直接跳出
每次找到下标之后让下标++,并在之后的范围内二分

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
int a[maxn];
map<int,int> mp;
int x[maxn];
int n,cnt;
int solve(int mid)
{
    int tmp=mid;
    int ans=0;
    int pos=1;
    while(true)
    {
        pos=lower_bound(x+pos,x+1+cnt,tmp)-x;
        if(pos==cnt+1) break;//没找到直接break
        ans+=tmp;
        tmp=tmp*2;//下一天需要的题目数翻倍
        pos++;
        if(pos==cnt+1||tmp>200000) break;//找到头或者不可能继续找
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        mp[a[i]]++;
    }
    map<int,int>::iterator it;
    for(it=mp.begin();it!=mp.end();++it)
    {
        x[++cnt]=(*it).second;
    }
    sort(x+1,x+1+cnt);//得到每种类型的数目,并排序
    int ans=0;
    for(int i=1;i<=200000;i++) ans=max(ans,solve(i));//O(n)枚举第一天的题目数
    printf("%d\n",ans);
    return 0;
}



F1. Pictures with Kittens

题意

给你n个点,每个点有个权值a[i],可以在n个点中选x个特殊点,要保证最后的序列中每连续k个点都至少有一个特殊点,问x个特殊点的权值和最大可以是多少
1&lt;=k,x&lt;=n&lt;=2001&lt;=k,x&lt;=n&lt;=2001<=k,x<=n<=200

做法

由于数据范围比较小,dp状态比较好定义
dp[i][j]表示前i个点中选j个特殊点最后一个特殊点放在i的最大权值和
转移方程也比较好写,但是要注意的是每个点只能从他之前k个点转移,不然不满足条件

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn = 205;
ll a[maxn];
ll dp[maxn][maxn];
int main()
{
    int n,k,x;
    scanf("%d%d%d",&n,&k,&x);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    if((2*k-1)*x<n)
    {
        printf("-1\n");
        return 0;
    }
    memset(dp,-0x7f,sizeof(dp));
    dp[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=min(i,x);j++)
        {
            for(int l=1;l<=k;l++)
            {
                if(i>=l) dp[i][j]=max(dp[i][j],dp[i-l][j-1]+a[i]);
            }
        }
    }
    ll ans=-1;
    for(int i=max(1,n-k+1);i<=n;i++)
    {
        ans=max(ans,dp[i][x]);
    }
    printf("%lld\n",ans);
    return 0;
}

F2. Pictures with Kittens

题意

给你n个点,每个点有个权值a[i],可以在n个点中选x个特殊点,要保证最后的序列中每连续k个点都至少有一个特殊点,问x个特殊点的权值和最大可以是多少
1&lt;=k,x&lt;=n&lt;=50001&lt;=k,x&lt;=n&lt;=50001<=k,x<=n<=5000

做法

dp[i][j]表示前i个点中选j个特殊点最后一个特殊点放在i的最大权值和
转移方程也比较好写,但是要注意的是每个点只能从他之前k个点转移,不然不满足条件
之前的n^3转移是铁定会超时的,我们反过来看之前的转移方程
dp[i][j]=max(dp[i][j],dp[i−l][j−1]+a[i]);1&lt;=l&lt;=kdp[i][j]=max(dp[i][j],dp[i-l][j-1]+a[i]); 1&lt;=l&lt;=kdp[i][j]=max(dp[i][j],dp[il][j1]+a[i]);1<=l<=k
也就是在j-1的dp状态内找与i相距l的状态中的最大值
这个过程我们可以用单调队列来简化
首先来想一下单调队列的最基础用法,也就是求固定长度滑动窗口的最大值
本题也是一样,只不过要对所有j开一个单调队列,维护dp[i][j]的最大值
而且转移的时候要注意内层for循环要从大到小,以免本层状态对本层转移有影响

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
#define dbg(x) cout<<#x<<" = "<< (x)<< endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
typedef long long ll;
typedef pair<int,long long > pil;
const int maxn = 5005;
ll a[maxn];
ll dp[maxn][maxn];
deque<pil> que[maxn];
int tail[maxn],head[maxn];
int main()
{
    int n,k,x;
    scanf("%d%d%d",&n,&k,&x);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    if((2*k-1)*x<n)
    {
        printf("-1\n");
        return 0;
    }
    memset(dp,-0x7f,sizeof(dp));
    dp[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        tail[i]=-1;
        head[i]=0;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=min(i,x);j>=1;j--)
        {
            if(j==1)
            {
                if(i<=k) dp[i][j]=a[i];
            }
            else
            {
                while(i-que[j-1].front().first+1>k+1) que[j-1].pop_front();
                dp[i][j]=que[j-1].front().second+a[i];
            }
            while(!que[j].empty()&&que[j].front().second<=dp[i][j]) que[j].pop_front();
            while(!que[j].empty()&&que[j].back().second<=dp[i][j]) que[j].pop_back();
            que[j].push_back(pil(i,dp[i][j]));
        }
    }
    ll ans=-1;
    for(int i=max(1,n-k+1);i<=n;i++)  ans=max(ans,dp[i][x]);
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值