Codeforces Round #696 (Div. 2)A-D题解

Codeforces Round #696 (Div. 2)A-D题解
比赛链接:https://codeforces.com/contest/1474

A题
贪心

中文题意:
总共t组数据(最大1e3)
每组数据给一个长度为n(最大1e5,且所有数据的n累加不超过1e5)的只包含0和1的字符串a。
你需要构造一个同样长度为n且只包含0和1的字符串b,字符串a和b每一位上加起来得到一个长度为n的字符串c。
之后字符串c中所有连续的相同字符都用1个该自己替换,得到最后的字符串d。
你需要输出一个字符串b,满足使用它进行上述运算,得到的字符串d作为一个整数来看最大。

思路:
我们要最后的字符串d作为一个整数来看最大,首先字符串c中如果两两相邻不存在相同的字符,则字符串d可以得到最大长度n。而一个整数尽可能大,第一要素自然就是它的位数最大(长度最长)。

字符串a和b中都只包含0和1,但是字符串c是可以通过1+1得到2的。
我们按照从左往右for一遍,按照字符串a的值,构造字符串b的值,满足c的当前位尽可能大,且不与前一位相同即可。
由于2可以构造,不论当前字符串a是0还是1,字符串c的值都有两种可选择,而不能与前一位相同这个条件最多否定掉一种选择。也就是说上述构造方案必然是存在的,因此按照上述规则施行即可。

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

int main()
{
    
    
    IOS
    int t;
    cin>>t;
    while(t--)
    {
    
    
        int n;cin>>n;
        string a;cin>>a;
        int pre=-1;
        for(int i=0;i<n;i++)
        {
    
    
            for(int j=2;j>=0;j--)//j代表当前位a+b的和c,从大到小枚举
            {
    
    
                if(j!=pre&&j-a[i]+'0'<=1)//需要满足不与上一位的值相等,
                {
    
    
                    cout<<j+'0'-a[i];//j+'0'变为对应字符类型,与a[i]相减得到b[i]对应的整数值
                    pre=j;//记录上一位的和为多少
                    break;
                }
            }
        }
        cout<<endl;
    }
}

B题
简单结论,复杂度分析

中文题面:
t组数据(最大3000)
每组数据给定一个整数d(1到10000)
你需要找到一个整数a满足
1.只有4个约数
2.任意两个约数之间的差值不能小于d
3.在所有满足上述两个条件的数中最小

思路:
我们先来解决什么数有4个约数
这里可以依靠质因数分解,不同质因数的指数取值不同来计算出总共有多少个约数。
利于一个数可以拆分成22 × \times × 33,也就是108。
那么它的约数就有3*4=12种,也就是2的指数取0,1,2三种,3的指数去0,1,2,3四种,组合起来。

依此可以得到有4个约数的数,
第一类:要么只有一个质因子,其指数为3
第二类:要么有两个质因子,指数均为1

我们先找到与1的差值不小于d的最小质数为x,
再找到与x的差值不小于d的最小指数为y

可以推得
第一类有4个约数的数中,最小的是x3
第二类有4个约数的数中,最小的是x × \times × y

显然,x × \times × y<x3

故我们只需要找到x和y即可。
由于d范围最大为1e4,因此y的最大值绝不可能超过3e4。
1000组数据,可以直接暴力枚举去找这两个质数x和y。
写线性筛的话复杂度3e7左右,不写的话也就再乘个根号n,仍然能过。

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

bool flag[30007];

void work()
{
    
    
    for(int i=2;i<30007;i++)
    {
    
    
        if(!flag[i])
        {
    
    
            for(int j=2;j*i<30007;j++) flag[i*j]=1;
        }
    }
}

int main()
{
    
    
    IOS
    int t;cin>>t;
    work();
    while(t--)
    {
    
    
        int d;cin>>d;
        int x=d+1;
        while(flag[x]) x++;
        int y=x+d;
        while(flag[y]) y++;
        cout<<x*y<<endl;
    }
}

C题
小结论,暴力,复杂度分析

中文题面:
总共t组数据(最大1e3)
每组数据给定一个整数n(最大1e3,且所有数据的n累加不超过1e3)
然后给定包含2n个正整数的数组。

你需要找到一个值x作为初始值,执行n次如下操作后使得这个长度为2n的数组清空:
1.选择当前数组中的两个数a和b满足a+b=x,将a和b从数组中除去
2.使得x的值变为max(a,b)

如果存在满足条件的x,则输出"YES"和x,并依次输出n次操作挑选的两个数。
如果不存在则输出"NO"

思路:
这里要先推一个小结论
对于当前数组中最大的值c,如果我们挑选的两个数不包含c的话,也就是说我们选择了a+b=x且a<c且b<c,那么在这次操作之后x的值会小于c。这就导致了c不可能在后续的操作中被除去。
由此得到结论,我们每次的操作都是挑选当前剩下的最大值c,然后找到x-c来与之组合。
更进一步的结论就是,当我们定了一个初始值x之后,后续的操作顺序实际上已经固定了。

注意到这道题n的范围很小,只有1e3。那么我们暴力枚举第一步操作时,与最大值组合的是哪一个数,有n种取法。
确定了第一步选择的两个数后,后续只要按照题目要求的操作去模拟即可。
这里只要不是写得太拉胯变成n3的做法都是不会tle的,下面放一个n2的做法。

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

int cas[1000000+7];//cas[i]标记i这个值出现了几次

int main()
{
    
    
    IOS
    int t;cin>>t;
    while(t--)
    {
    
    
        int n;cin>>n;
        vector<int>num(2*n);
        for(int i=0;i<2*n;i++) cin>>num[i];
        sort(num.begin(),num.end());
        bool F=0;//标记是否存在可行解
        int st=0,sum;
        vector<pair<int,int>>out;//保存每步取出了哪两个数,输出用
        for(int o=0;o+1<2*n;o++)//枚举第一步操作时,与最大值向加的数的位置
        {
    
    
            for(int i=0;i<2*n;i++) cas[num[i]]++;
            int tar=2*n-1;
            st=sum=num[tar]+num[o];//st记录第一步的和,用于输出,sum记录当前需要找的和为多少
            F=1;
            for(int i=0;i<n;i++)
            {
    
    
                while(cas[num[tar]]==0) tar--;
                cas[num[tar]]--;//当前最大的数减去一个
                cas[sum-num[tar]]--;//与当前最大的数加起来等于sum的数减去一个
                if(cas[sum-num[tar]]<0||cas[num[tar]]<0)
                {
    
    
                    cas[sum-num[tar]]=cas[num[tar]]=0;//这里的清零和上面if判断的cas[num[tar]]
                    //都是容易疏忽的点
                    F=0;
                    break;
                }
                out.push_back({
    
    sum-num[tar],num[tar]});
                sum=num[tar];
            }
            if(F) break;
            for(int i=0;i<2*n;i++) cas[num[i]]=0;//清零操作
            out.clear();
        }
        if(F)
        {
    
    
            cout<<"YES"<<endl;
            cout<<st<<endl;
            for(int i=0;i<n;i++) cout<<out[i].first<<' '<<out[i].second<<endl;
        }
        else cout<<"NO"<<endl;
    }
}

D题
结论,整体思维

中文题意:
t组数据(最大1e4)
每组数据给定一个整数n(2到2e5),之后依次给定n个整数。

现在需要你进行判断
在操作开始前最多交换一对相邻整数的位置后
每次操作可以使得两个相邻整数均减去1,能否在若干次操作后使得整个数组的值均为0

思路:
首先在不考虑交换一对相邻整数的情况下,如何来判断当前的数组能否被清空呢?
我们使用num[]数组依次保存初始值
从左往右依次去清空,使用rest[]数组保存当前位置在左侧全被清空后,仍然剩下的需要与右侧进行一同删去的值。
转移方程为rest[i]=num[i]=rest[i-1]
检测下最后一个位置是否还有剩余的值,也就是rest[n]是否为0,就可以判断整个数组能否被清空了。

这一步完成后,我们需要思考,在初始数组无法被清空的情况下,交换怎样的一对相邻数值能使得整个数组被清零呢?

这里我们可以先去思考,更改相邻的两个数值x和y(y在x右侧),令x的位置下标为now,对整个数组会产生什么影响。
我们把x分成两个部分,x=a+b,ret[now]=b
a代表x与左侧位置下标now-1的值组合一同删去的部分
b代表x与右侧的y组合一同删去的部分
同理我们也可以吧y分成两个部分y=b+c,rest[now+1]=c

那么交换x和y的位置后,变成了y在左侧,x在右侧。
y仍然要拿出a用来清除下标now-1剩下的部分,那么y此时剩下的部分为y-a,也就是rest[now]=y-a=b+c-a=b+(y-x)
x要拿出b+c-a的部分来清除rest[now],那么剩下的部分就是rest[now+1]=a+b-(b+c-a)=a-(c-a)=a-(y-x)=c-2(y-x)
注意到
rest[now]前后从b变为b+(y-x)

rest[now+1]前后从c变为c-2(y-x)

也就是rest[now]和rest[now+1]分别增加和减少了y与x的差值。
而下标now左侧的部分,由于我们采取从左向右的处理方式,不会受到这次交换的影响,而now+1右侧的部分是会受到影响的。
rest[now+1]减少了2(y-x)的值,那么对应的rest[now+2]就会增加2(y-x)的值,再rest[now+3]对应减少2(y-x)的值…一直作用到最后末尾位置,交替增加和减少2(y-x)。

如果有某一对相邻数值差为y-x,且满足2(y-x)等于rest[n],并且更改后,整个rest数组不出现非法的负数,即为可清零整个数组的情况。

如果我们暴力枚举的话,复杂度为n2达到4e10,无法接受。
这时候我们注意到对于终点的rest[n]来说,我们的目标是把它清零,也就是y-x的差值是固定的rest[n]/2,我们可以从末尾反向依照上面讨论的规律,去找到满足条件的位置即可,并且一路上修改后的rest数组不能为非法的负数。
由此可以得到一个O(n)的线性解法,代码如下:

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

int num[200007];//记录每个位置初始值
int rest[200007];//rest[i]代表从左往右清空i位置前的所有值后,i位置剩下的值,也就是需要与i+1位置一起删除的值
int n;

int main()
{
    
    
    IOS
    int t;cin>>t;
    while(t--)
    {
    
    
        cin>>n;
        for(int i=1;i<=n;i++) cin>>num[i];
        int tar=0;//记录rest第一次出现非法的位置
        for(int i=1;i<=n;i++)//从左往右依次清空
        {
    
    
            if(rest[i-1]>num[i]&&!tar) tar=i;
            rest[i]=num[i]-rest[i-1];
        }
        if(!tar&&rest[n]>0) tar=n+1;//如果最后一个位置仍然有值待处理,仍然是无法不更改顺序清空的情况
        //注意这里要加个tar不为0,否则tar记录的第一次非法位置可能被改变
        if(tar)//如果存在非法位置
        {
    
    
            if(rest[n]%2) cout<<"NO"<<endl;//如果最后剩下的是奇数,由于每次操作减去的值为2是个偶数,最后都不可能被清零
            else
            {
    
    
                int flow=rest[n]/2;//记录需要交换位置的相邻两个数的差值
                bool f=0;
                for(int i=n;i>1;i--)
                {
    
    
                    rest[i]-=flow*2;//当前位置的rest减去两倍flow
                    if(rest[i]<0) break;//rest不能为负数
                    if(rest[i-1]+flow>=0&&num[i]-num[i-1]==flow&&tar>=i-1) {
    
    f=1;break;}
                    //找到了相邻两个位置差值为flow,并且位置在第一个(也就是最左边)非法位置的左边
                    //代表之前的非法flow位置也已经在上述过程中合法了,整体满足要求
                    flow=-flow;//移动一个位置后flow变为负数,很好理解,当前位置剩下的值变多了x,那么下个位置剩下的值就会减少x
                }
                if(f) cout<<"YES"<<endl;
                else cout<<"NO"<<endl;
            }
        }
        else cout<<"YES"<<endl;//不存在非法位置且最后被清零,直接输出
    }
}

猜你喜欢

转载自blog.csdn.net/StandNotAlone/article/details/112984350
今日推荐