Codeforces Round #822 (Div. 2)

A

题意

给一个长为n的数组,每次可以对其中某个数做+1-1的操作。求最小的操作次数,使得可以从数组中选出三个相同的数。

思路

很容易可以想到选三个最接近的数然后操作。也可以很容易证明,对于a<b<c,一定是将a,c操作到b距离最短。

所以排序后遍历中间的b,然后求ac的差距即可。

代码

#include<bits/stdc++.h>
 
using namespace std;
 
int a[305];
 
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,minn=2e9+7;
        scanf("%d",&n);
        for(int i=0;i<n;i++)
            scanf("%d",&a[i]);
        sort(a,a+n);
        for(int i=1;i<n-1;i++)
            minn=min(minn,a[i+1]-a[i-1]);
        printf("%d\n",minn);
    }
}

B

题意

乱七八糟一大堆,最后只要找规律构造就行了。

思路

找规律

代码

#include<bits/stdc++.h>
 
using namespace std;
 
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        printf("1\n");
        for(int i=2;i<=n;i++)
        {
            printf("1 ");
            for(int j=0;j<i-2;j++)
                printf("0 ");
            printf("1\n");
        }
    }
}

C

题意

给一个包含1-n整数的集合S,每次可以花费代价k进行一次操作:选择Sk的最小倍数,然后移除该数。

然后给一个S的子集T,求通过上述操作把S变成T的最小代价。

思路

思路很明显是要贪心的。对于一个待移除的数A,一定是选择他的最小因子来移除最优。如果从因子k的角度考虑,那么一个更小的k能移除的数越多越好。所以可以想到从1-n遍历因子,尽可能移除更多的数。然后很容易可以想到,只要移除到一个不需要移除的数,那么就可以停止了。然后实现一下,已经移除的数打标记,不计入答案就好了。总体复杂度为n+n/2+n/3+....+n/(n-1)+1,可以算一下时间复杂度大约为O(n*logn)

代码

#include<bits/stdc++.h>
 
using namespace std;
using ll=long long;
 
const int MAX=1e6+6;
 
char s[MAX];
bool vis[MAX];
 
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        ll sum=0;
        scanf("%d%s",&n,s+1);
        for(int i=1;i<=n;i++)
            vis[i]=0;
        for(int i=1;i<=n;i++)
        {
            if(s[i]=='0')
            {
                if(!vis[i])
                    sum+=i,vis[i]=1;
                for(int j=i+i;j<=n;j+=i)
                {
                    if(s[j]=='1')break;
                    if(!vis[j])
                        sum+=i,vis[j]=1;
                }
            }
        }
        printf("%I64d\n",sum);
    }
}

D

题意

给一个数组a,初始位于i位置,初始分数为ai,每次可以任意左右移动,每移动到一个新的位置j,就将分数加上aj,如果分数为负,则失败,如果最后可以移动到数组头或尾且保持分数非负,则获胜。求最后能否获胜。

思路

如果某一段和为非负,那么一定可以移动到它的边界,将和吸收掉,分数一定不降。但是过程中要保持分数非负,所以要想吸收某一段,当前分数必须大于abs(该段的最小前缀和。如果左右两侧都不能吸收了,那一定会失败,如果存在一段可以吸收的,那吸收一定保持分数不降。所以只需要从起点处预处理出左右分段,然后左右尝试能否吸收就能得出答案了。实现可以用队列加pair方便一点。

代码

#include<bits/stdc++.h>
 
using namespace std;
using ll=long long;
using pll=pair<ll,ll>;
 
const int MAX=2e5+5;
 
int a[MAX];
 
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,k;
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        int L=k,R=k;
        queue<pll> QL,QR;
        while(!QL.empty())QL.pop();
        while(!QR.empty())QR.pop();
        while(R<n)
        {
            int r=R+1;
            ll sumr=a[r],minr=a[r];
            while(sumr<0&&r<n)
            {
                r++;
                sumr+=a[r];
                minr=min(minr,sumr);
            }
            QR.push({sumr,-minr});
            R=r;
        }
        while(L>1)
        {
            int l=L-1;
            ll suml=a[l],minl=a[l];
            while(suml<0&&l>1)
            {
                l--;
                suml+=a[l];
                minl=min(minl,suml);
            }
            QL.push({suml,-minl});
            L=l;
        }
        ll cur=a[k];
        QL.push({0,0});
        QR.push({0,0});
        while(!QL.empty()&&!QR.empty())
        {
            int fail=0;
            if(cur<0)break;
            pll p=QL.front();
            if(cur>=p.second)
            {
                QL.pop();
                cur+=p.first;
            }
            else fail++;
            p=QR.front();
            if(cur>=p.second)
            {
                QR.pop();
                cur+=p.first;
            }
            else fail++;
            if(fail==2)break;
        }
        if(QL.empty()||QR.empty())printf("YES\n");
        else printf("NO\n");
    }
}
posted @ 2022-10-08 22:55  cryingrain  阅读(21)  评论(0编辑  收藏  举报