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

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

//写于rating值2032/2056
//昨晚炸了又没能打成,早上补了一波A-E
比赛链接:https://codeforces.com/contest/1389
A题
简单数学
我们需要寻找一对x和y满足l<=xLCM(x,y)<=r。
首先我们规定x是x和y中较小的那一个,有一个简单的结论,LCM(x,y)=kx,也就是说LCM(x,y)必然是x的整数倍(废话),由于x!=y,因此k必须满足,k>1。也就是说LCM(x,y)至少为2x,只要2x<=r则必然有解,此时y同样取2x即可。
既然我们希望x满足2x<=r,那么x要尽可能取小,所以x取最小值l,判断l*2是否小于等于r即可。

#include
#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 l,r;
        cin>>l>>r;
        if(l*2<=r) cout<<l<<' '<<l*2<<endl;
        else cout<<-1<<' '<<-1<<endl;
    }
}

B题
暴力,贪心,小结论
num[i]存储第i个数字是多少,sum[i]代表前i个数字的前缀和,he[i]代表num[i]+num[i-1]的值
首先我们考虑k次操作一直往右走的情况,那么总共能得到的分数就是sum[1+k]
接着再考虑存在往左走的情况:
每选择往左走一次,最后k次操作后我们所在的下标位置会-2。
也就是说如果我们往左走了i次,那么我们最后所在的下标位置会是1+k-2*i。
注意题目中限定了我们往左走的操作是不能连续的,也就是说我们每往左走了一步就要马上回头。
以长度n=5,k=4为例
一直往右走所走过的下标为1,2,3,4,5
如果我们选择在走到下标3的位置往左,那么结果会变成1,2,3,2,3
注意到我们最后所在的下标变成了1+k-2=3,其中下标2和下标3的数字计算了两次。

总结一下往左走一次所带来的的影响是:
1.最后所在下标位置-2。
2.如果在第i个位置选择往左走,那么i-1和i这两个位置的数会被多取一次

再注意到题目中一个特别的数据范围, 0≤≤(5,),往左走的次数最多不会超过5。
所以我们直接暴力枚举往左走了i次,然后计算出最后的位置下标lim,计算j取1到lim+1的下标位置他们he[j]中哪一个最大,贪心取i次he[j]即可。
至于这里的j为什么右侧可以去到lim+1而不是lim,结合下面这个例子理解。
长度n=6,k=4
我们可以在位置5往左走一次,经过的下标依次为1,2,3,4,5,4
注意这里最后所在的位置是4,但是我们是可以在4+1=5的位置回头往左走的。

n的和不超过3e5,z不超过5,复杂度也就是15e5的级别,暴力完全足够。

#include
#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 n,k,z;
        cin>>n>>k>>z;
        vector<ll>num(n+1),sum(n+1),he(n+1);
        num[0]=sum[0]=he[0]=0;
        for(ll i=1;i<=n;i++)
        {
            cin>>num[i];
            sum[i]=sum[i-1]+num[i];
            he[i]=num[i]+num[i-1];
        }
        ll ans=sum[k+1];
        for(ll i=1;i<=z&&k+1-2*i>=0;i++)//这里要判断下k+1-2*i是不是超过下标界限了
        {
            ll lim=k+1-2*i;
            ll temp=sum[lim];
            ll Max=-1;
            for(ll j=1;j<=lim+1;j++)
            {
                if(he[j]>Max)
                {
                    Max=he[j];
                }
            }
            ans=max(ans,temp+Max*i);
        }
        cout<<ans<<endl;
    }
}

C题
字符串,暴力,小结论
往左移动之后的下标为2 3 t4 t5… −1 1
往右移动之后的下标为 1 2 3… tn-3 tn-2 −1
对比之后我们很容易发现,往左和往右移动之后的字符串相同,等价于原字符串中
s[i]=s[i+2]
也就是奇数下标的字符要全部相同,偶数下标的字符也要全部相同
并且由于下标是循环的,当字符串长度n是奇数的时候的时候,会有s[n]=s[2],此时n是奇数而2是偶数,这种情况下会变成整个字符串必须由相同的字符构成。

综上,最后构造的字符串长度n为奇数的时候,整个字符串必须只有一个相同的字符。
当n为偶数的时候,整个字符串需要是a和b两个字符交替出现。

注意到所有输入数据的字符串长度不超过2e5,字符只会是0-9的十个数字,因此我们直接暴力枚举a和b分别取0-9即可,复杂度2e7级别。
注意特判当a和b不相同的时候,最后构造的字符串长度n必须是偶数。

#include
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define llINF 9223372036854775807
using namespace std;
int32_t main()
{
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
        string s;
        cin>>s;
        ll len=s.size();
        ll ans=llINF;
        for(ll i=0;i<10;i++)
            for(ll j=0;j<10;j++)
            {
                ll temp=0,tar=0;//tar为当前位置下标,tamp为删除了几个字符
                while(tar<len)
                {
                    while(tar<len&&s[tar]-'0'!=i) tar++,temp++;
                    tar++;
                    while(tar<len&&s[tar]-'0'!=j) tar++,temp++;
                    tar++;
                }
                if(i!=j&&((len-temp)&1)) temp++;//当奇数和偶数下标的字符不相同,且最后得到的字符串长度是奇数的时候,要再多删掉一个字符把字符串变成偶数长度
                ans=min(ans,temp);
            }
        cout<<ans<<endl;
    }
}

D题
贪心,分类讨论
一开始的时候我们有n段a区间为[l1,r1]和n段b区间[l2,r2],我们每次可以使其中的某一段区间往左或者往右延伸一个长度。
我们的目标是对于,对于累加对于下标1-n,ai和bi的相交长度和要大于等于目标值k。
首先按照初始给定的两个长度是否存在相交的长度分为两大类来讨论:
先预处理一下,如果r1>r2的话,交换两个长度区间,保证r1<=r2,方便后面讨论两个长度区间是否存在相交部分。
第一类,两个初始长度存在相交部分
两个区间相交部分的长度记为now,两个区间剩余长度为temp,我们初始的时候就已经拥有了now × \times ×
n的相交总长度,如果这个值仍然小于k的话,我们仍然需要构造出k-now × \times ×n的相交长度。
操作1对于每对初始区间来说,还拥有temp的剩余长度,这些长度我们只需要延伸两个初始区间的某一段的1个长度,即可获得1个对应相交长度。也就是我们的延伸次数和构造得到的相交长度比例是1:1。
举例来说,初始区间为[2,6]和[4,9]
相交长度为[4,6]为2,剩余部分为[2,4]和[6,9]共5。
也就是对于[2,6]和[4,9]这对初始区间,我们在接下来的5次延伸操作中,都是可以做到延伸一次便获得一个相交长度的。
比如延伸成[2,7]和[4,9]或者延伸成[2,6]和[3,9]都可以使相交长度从2+1变成3。
延伸3次[2,6]的右侧和延伸2次[4,9]的左侧共延伸5次后得到[2,9]和[2,9],总相交长度变为7。
操作2此时针对这一段区间,如果我们还想继续构造相交长度,我们则必然要使用两次延伸才能得到一个相交长度。比如各自延伸1次右侧得到[2,10]和[2,10]或者各自延伸1次左侧得到[1,9]和[1,9]。可以观察到此时的延伸次数和构造得到的相交长度比例为2:1。效率低于上面的操作1。
因此我们采取贪心的策略,尽可能使用操作1,直到这n对区间的操作1都被使用完毕,再使用操作2
第二类,两个初始长度不存在相交部分
在最初始的时候,如果两个区间不仅不存在相交部分,且边界也不相邻,那么上一类情况中的操作1和操作2都无法实现。因此我们最开始的时候必然要先使得至少一组初始区间的边界相邻。
用need代表把两个初始区间延伸为边界相邻需要的延伸次数,get为每一组区间在延伸为边界相邻后得到的可以进行操作1的次数,cas记录当前已经构造了多少组边界相邻的区间。
举例来说,初始区间为[2,4]和[5,6]。
此时need=1,延伸1次后就可以使得这两个区间边界相邻,比如延伸1次[2,4]的右侧构造[2,5]和[5,6]或者延伸一次[5,6]的左侧构造[2,4]和[4,6]。在这之后我们会得到get=6-2=4次的操作1的次数。
我们继续采取贪心的策略
最开始的时候cas=0,我们必然要执行一次need,再这之后取min(get,k),需要构造的k-=min(get,k)继续循环。
如果k,代表我们再多构造一组相邻区间+操作1,也就是执行need+k次的延伸是大于2 × \times ×k次的,如果此时cas>1也就是代表我们有进行操作2的选择能力的话(前面已经有相邻区间供进行操作2),我们直接加上2 × \times ×k次即可。
另外如果cas==n,也就是代表没有多余的初始区间让我们去执行操作1了,此时也直接加上2 × \times ×k

#include
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define llINF 9223372036854775807
using namespace std;
int32_t main()
{
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
        ll n,k;
        cin>>n>>k;
        ll l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        if(r1>r2)//保证r1<=r2方便下面讨论[l1,r1]和[l2,r2]是否存在相交部分
        {
            swap(l1,l2);
            swap(r1,r2);
        }
        if(r1>=l2)//第一类情况,初始区间便存在相交部分
        {
            ll now,temp;
            if(l1>=l2)
            {
                now=r1-l1;
                temp=r2-r1+l1-l2;
            }
            else
            {
                now=r1-l2;
                temp=r2-r1+l2-l1;
            }
            now*=n;//now为目前的相交总长度
            temp*=n;//temp为操作1能进行的最多次数
            if(now>=k) cout<<0<<endl;
            else
            {
                k-=now;
                if(k<=temp) cout<<k<<endl;
                else cout<<temp+(k-temp)*2<<endl;
            }
        }
        else//第二类情况,初始区间不存在相交部分
        {
            ll need=l2-r1;//把初始一对区间延伸至边界相邻需要的延伸次数
            ll get=r2-l1;//初始一对区间延伸至边界相邻后,我们得到的可以进行操作1的次数
            ll ans=0;
            ll cas=0;//记录已经合并了多少对初始区间
            while(k)
            {
                if(cas==n)
                {
                    ans+=k*2;
                    k=0;
                }
                else if(k<need&&cas)//如果k
                {						//cas>0是我们能进行操作2的必须条件
                    ans+=k*2;
                    k=0;
                }
                else
                {
                    cas++;
                    ans+=need+min(get,k);
                    k-=min(get,k);
                }
            }
            cout<<ans<<endl;
        }
    }
}

E题
数论
一年有m个月,每个月有d天,每个星期w天。
现在要你求有多少对(x,y)满足x 注意到第x月第y天是一年中的第x × \times ×d+y天,第y月第x天是一年中的第y × \times ×d+x天。
在一星期中对应的下标相同,其实也就是x × \times ×d+y和y × \times ×d+x对于w同余,也就是(x × \times ×d+y)%w==(y × \times ×d+x)%w
移项后可以得到(x-y)(d-1)%w==0,也就是(x-y)(d-1)==kw。
问题转化为了x-y的值要满足乘以d-1后是w的整数倍。
记temp=lcm(d-1,w)/(d-1),那么x-y的差值dis便是temp的整数倍。
接着在1<=x<=m和1<=y<=d中找寻有多少对(x,y)满足x-y是temp的整数倍,且x>y,且第x月第y天和第y月第x天在一个星期中对应相同。
注意到这里x和y本质上是等价的,因此范围可以限制可以统一为1<=x<=min(m,d),1<=y<=min(m,d)。
记此时的右边界为d=min(m,d)
对于某一个确定的x和y的差值dis来说
我们可以取的最小的x是1,对应的x和y是(1,1+dis)
我们可以取的最大的x是d-temp,对应的x和y是(d-dis,d)
总共有d-dis种。
而dis的取值范围,记n1=d/temp,那么dis的取值就是1 × \times ×temp,2 × \times ×temp…n1 × \times ×temp(dis不可超过d的范围)共n1种。
注意到这n1种dis对应的情况各自都为d-dis种,实际上就是d-1 × \times ×temp,d-2 × \times ×temp…d-n1 × \times ×temp,实际上就是个等差数列求和。
和等于[(d-1 × \times ×temp)+(d-n1 × \times ×temp)]*n1/2。

#include
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#define llINF 9223372036854775807
using namespace std;

ll gcd(ll a,ll b)
{
    return b?gcd(b,a%b):a;
}

int32_t main()
{
    IOS;
    int t;
    cin>>t;
    while(t--)
    {
        ll m,d,w;
        cin>>m>>d>>w;
        if(d==1) cout<<0<<endl;
        else
        {
            ll temp=w/gcd(d-1,w);
            ll ans=0;
            d=min(m,d);
            ll n1=d/temp;
            ans=(d-temp+d-n1*temp)*n1/2;
            cout<<ans<<endl;
        }
    }
}

你可能感兴趣的:(codeforces,算法)