Educational Codeforces Round 94 (Rated for Div. 2)A-E题解

Educational Codeforces Round 94 (Rated for Div. 2)A-E题解
//写于rating值2075/2184
//科三考完了,恢复正常训练
//下一场cf会开始正常打
//这一场的E和前段时间刚写过的一道题一模一样…

比赛链接:https://codeforces.com/contest/1400
A题
简单构造

给定一个长度为2n-1的只包含0和1的字符串a,现在需要你构造一个长度为n的字符串b,满足与a当中长度为n的任意连续子串,都至少有一个位置上的数字是相同的。

注意到字符串a的长度是2n-1,有一个特殊的位置就是中间的那个,第n个字符。这个字符在字符串a的长度为n的任意连续子串中均有出现。
而这个字符在这些连续子串中出现的位置,刚好满足了1-n这n个位置。
我们直接构造一个长度n的字符串,全部为a字符串中第n个字符即可。

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;
        cin>>n;
        string s;
        cin>>s;
        char c=s[n-1];
        for(int i=0;i<n;i++) cout<<c;
        cout<<endl;
    }
}

B题
贪心,暴力

题意为有两个人,各自有一个背包容量。现在有cnts把大小为s的剑,和cntw把大小为w的斧头,现在询问你这两个人最多可以拿走多少把武器。

首先进行一个贪心的过程,假设s<=w,也就是剑的重量不大于斧头,此时我们应该尽可能多得去拿剑。
证明如下:
如果某一种方案,剑没有被拿完,但是有拿了斧头。由于剑的重量不大于斧头,因此我们那的任意把斧头都是可以换成等数量的剑,还可以多空余出空间来。
由此证明优先选择剑是正确的贪心。

然后注意到剑和斧头的数量都在2e5之内,我们可以暴力枚举第一个人拿了多少把剑,每次枚举执行贪心过程即可。

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        ll p,f;
        ll cnts,cntw,s,w;
        cin>>p>>f>>cnts>>cntw>>s>>w;
        ll ans=0;
        if(s>w) swap(cnts,cntw),swap(s,w);
        for(ll i=0;i*s<=p&&i<=cnts;i++)
        {
    
    
            ll first_s,first_w,second_s,second_w;//first代表第一个人拿的道具,s代表剑,w代表斧头
            first_s=i;//第一个人拿走i把剑
            second_s=min(f/s,cnts-first_s);//第二个人尽可能多拿剑,但是拿的个数不能超过剩下的剑的数量
            first_w=min((p-first_s*s)/w,cntw);//第一个人剩余的背包容量,尽可能多得拿斧头
            second_w=min((f-second_s*s)/w,cntw-first_w);//第二个人接着拿剩下的斧头,注意背包容量是剩余的,斧头个数也是剩余的
            ans=max(ans,first_s+first_w+second_s+second_w);
        }
        cout<<ans<<endl;
    }
}

C题
构造,施行

题意为给定一个长度为n的只包含0和1的字符串a以及一个距离值x,你需要构造一个长度为n的只包含0和1的字符串b,使得字符串a可以从字符串b按照如下规则得到:
对于字符串a中的第i个字符来说,如果i+x或者i-x的下标在1-n之间,且字符串b中的第i+x或者第i-x个字符是1,那么字符串a中的第i个字符也必须是1,其他情况下为0。

首先由题意给定的x和n的数值大小关系,可以知道对于任意的i,i+x和i-x中至少有一个是在1-n范围内的。
对于a[i]来说:
如果a[i]=0,那么b[i-x]和b[i+x]也必须是0
如果a[i]=1,那么b[i-x]和b[i+x]中至少有一个要是1

接着就是构造的过程了,我们需要想到一种构造方法,当前构造的位置不会再对我们前面已经构造的位置产生影响。
注意到这道题的特殊规则,对于a[i]来说,我们需要在意的位置仅有两个,为b[i-x]和b[i+x]。
对于不同的i值,比如i1<i2,i1+x和i2+x是不相等的,i1-x和i2-x也是不相等的,只有i1+x和i2-x是有可能相等的。

我们可以按照a中的下标i从小到大,去构造对应b中的i-x和i+x。
构造过程中,对于当前的位置i来说,b[i+x]在之前的构造过程中是未被构造过的,而b[i-x]是可能被构造过的。
如果a[i]=1,那么我们需要检测b[i-x]和b[i+x]是否存在1,如果不存在1,又是否有待构造的位置给我们去构造1,如果都不满足则无法构造目标。有位置可以构造的时候,如果b[i-x]可以构造就构造b[i-x]为1,不能构造再去构造b[i+x],因为b[i+x]是会对a[i+2x]的构造产生影响的,我们采取贪心的策略应当优先构造左侧。
如果a[i]=0,那么我们需要检测b[i-x]和b[i+x]是否都为0,并把这两个位置都构造0,如果不满足则无法构造目标。

按照这个构造思路去施行即可。

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        string s1,s2;
        cin>>s1;
        int n=(int)s1.size();
        s2.resize(n,'!');//s2为我们构造的字符串,一开始全部置为字符'!'代表该位置为待构造
        int x;
        cin>>x;
        bool flag=1;//指示目标字符串能否被构造,1代表可以
        for(int i=0;i<n;i++)//s1字符串中按照下标从小到大依次扫过去
        {
    
    
            if(s1[i]=='0')//当s1[i]=0的时候,s2[i-x]和s2[i+x]必须都为0
            {
    
    //由于s2[i+x]不可能在前面的构造过程中被构造过,因此第一个判断可行性的if里面没考虑
                if(i-x>=0&&s2[i-x]=='1') flag=0;//如果s2[i-x]在之前的构造过程中被构造了1,那么与当前条件矛盾
                if(i-x>=0) s2[i-x]='0';//构造左右两个位置为0
                if(i+x<n) s2[i+x]='0';
            }
            else
            {
    
    
                bool f=0;
                if(i-x>=0&&s2[i-x]=='1') f=1;//检测左右位置是否已经存在1
                if(!f)//如果不存在则需进行构造1
                {
    
    
                    if(i-x>=0&&s2[i-x]=='!') s2[i-x]='1';//优先构造左侧
                    else if(i+x<n) s2[i+x]='1';//构造右侧
                    else flag=0;//两侧都无法构造,则产生矛盾,目标字符串无法构造
                }
            }
        }
        for(int i=max(n-x,0);i<n;i++) if(s2[i]=='!') s2[i]='0';
        //注意上述构造过程结束后,目标字符串中的末尾部分是可能存在未构造部分的,任意构造均可
        //具体原因不表,思考上述构造过程即可
        if(flag) cout<<s2<<endl;
        else cout<<-1<<endl;
    }
}

D题
暴力,前缀和

给定一个长度为n的数列a(n<=3000),你需要计算出有多少对(i,j,k,l)满足1<=i<j<k<l<=n,且a[i]=a[k],a[j]=a[l]。

注意到这道题的n非常小,所以我们可以采取一些相对比较暴力的做法。
这里由于有两对相等数字,四个下标,因此我们至少要找两个下标作为初始固定位置(时间复杂度消耗为n2,1e6级别)。
如果我们固定的是i和j,或者k和l的话(也就是固定两对相等数字的左侧,或者右侧),以固定i和j为例,我们接着考虑k和l满足的有几种,会发现此时非常困难,因为我们既要考虑到a[i]=a[k],a[j]=a[l],还要考虑k<l,而此时k和l的限制都是大于j,难以计算情况数量
接着我们考虑其中一对相等数字取作左侧下标,另一对相等数字取右侧下标。我们考虑固定j和k,再去找i和l。此时i的位置一定是小于j,l的位置一定是大于k,由于j<k,也就是意味着满足上述条件时,i一定是小于l的。
我们可以用sum[i][j]记录数列前i个数字中(由于n特别小,时空复杂度均为1e6而已毫无问题),数字j出现了几次,也就是前缀和数组。当我们固定了下标j和k后,我们使用前缀和数组查询下标小于j的数字中等于a[k]的有几个,记为x,再查询下标大于k的数字中等于a[j]的有几个,记为y。左右两部分,两两任意配对均为一种答案,因此累加的答案即为x × \times × y。

由此,我们预处理出前缀和数组sum(1e6复杂度),再暴力枚举j和k的下标(1e6复杂度),使用前缀和数组计算每种情况下(O(1)),i和l有多少种方案累加到ans即可。

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const ll maxn=3e3+7;

int n;
int num[maxn];
int sum[maxn][maxn];//前缀和数组,sum[i][j]记录num数列中的前i个数字中,j出现了几次

int32_t main()
{
    
    
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
    
    
        for(int i=0;i<n;i++)
            for(int j=1;j<=n;j++) sum[i][j]=0;//不要用memset,避免tle,清理上次循环用过的部分即可
        ll ans=0;
        cin>>n;
        for(int i=0;i<n;i++)
        {
    
    
            cin>>num[i];
            if(i)
                for(int j=1;j<=n;j++)//先继承前i-1个数字中,各个数字的出现情况
                    sum[i][j]=sum[i-1][j];
            sum[i][num[i]]++;//当前位置的数字个数+1
        }
        for(int j=1;j<n;j++)
            for(int k=j+1;k<n;k++)
            {
    
    
                int i=sum[j-1][num[k]];//i的位置需要满足小于j,且对应数字等于num[k]
                int l=sum[n-1][num[j]]-sum[k][num[j]];//l的位置需要满足大于k,且对应数字等于num[j]
                ans+=i*l;
            }
        cout<<ans<<endl;
    }
}

E题
贪心,结论,分治,dfs

//前段时间集训刚写过一模一样的题…没记错应该也是cf上的

题意为给定一个长度为n的只包含自然数的数列(n<=5000),你有两种操作方式:
第一种为挑选某一个数字,将这个数字变为0。
第二种为挑选一个连续的区域,其中的每个数字需要满足均不为0,使得每个数字的值-1。
现在问你需要最少多少次操作,能将整个数列全部置为0。

对于一段长度n的数列来说,如果我们不采取第二种操作,只用第一种操作的话,需要的操作次数就是n。

接着再考虑使用第二种操作的同时,也使用第一种操作的情况。
定义Min为当前数列中最小的数字,也是我们当前能对1-n整个数列下标均使用第一种操作的最大次数。
首先贪心过程,我们每次进行第二种操作,肯定尽可能选择长度更长的部分,否则都是存在次数相同或者更优的方案。

之后是我们需要对1-n整个数列下标使用几次第二种操作。这里的思考过程,对于我们进行x次第二种操作,x<Min,也就是说还有继续进行第二种操作的空间。此时我们接下来只进行第一种操作,由于数列中每个数都是大于等于Min的,因此此时每个数字都是不为0的,我们还需要进行n次第一种操作。
此时的总操作数为x+n,与我们直接对每个位置进行第一种操作的总操作次数x要多。
由此我们又得到一个贪心结论,对于一个长度为n的数列来说,其中的最小值为Min,要么执行n次第一种操作,要么执行Min次第二种操作后,继续循环执行上述操作。执行Min次第二种操作后,原数列会被分割成若干个连续的非0子序列,对每段连续非0子序列递归执行上述过程即可。

#include<bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define llINF 9223372036854775807
using namespace std;

ll n;
vector<ll>num;

ll dfs(vector<ll>now)
{
    
    
    ll temp=0,len=now.size();//temp为对整个数列now使用Min次第一种操作后,对剩余部分清零还需要的操作次数
    ll Min=llINF;
    for(ll i=0;i<len;i++)
        Min=min(Min,now[i]);
    vector<ll>next;
    for(ll i=0;i<len;i++)
    {
    
    
        if(now[i]==Min)
        {
    
    
            if(next.size())
            {
    
    
                temp+=dfs(next);//递归计算进行Min次操作后,剩下的连续非0部分还需多少次最少操作
                next.clear();
            }
        }
        else next.push_back(now[i]-Min);
    }
    if(next.size()) temp+=dfs(next);
    return min(len,Min+temp);//len为只进行第二种操作的操作次数,即为数列now长度
}

int32_t main()
{
    
    
    IOS;
    cin>>n;
    num.resize(n);
    for(auto &x:num) cin>>x;
    cout<<min(n,dfs(num))<<endl;//原数列不一定是连续的非0序列,但是这种写法的dfs是可以处理这种情况的
}

猜你喜欢

转载自blog.csdn.net/StandNotAlone/article/details/108294510