牛客周赛 Round 22

A:小红的漂亮串

题目描述

小红定义一个字符串是漂亮串,当且仅当其至少包含两个"red"子串。

现在小红拿到了一个字符串s,请你帮小红判断它到底是不是漂亮串。

1≤len(s)≤100

输入描述:

一个仅包含小写字母的字符串。

输出描述:

如果该字符串是漂亮串,请输出"Yes"。否则输出"No"。

示例1

输入

redered

输出

Yes

示例2

输入

reredd

输出

No

解题思路:

直接遍历一遍就行了

cpp代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
    string s;
    cin>>s;
    int cnt=0;
    int n=s.size();
    for(int i=0;i<n-2;)
    {
        if(s.substr(i,3)=="red")cnt++,i+=3;
        else i++;
    }
    if(cnt>=2)puts("Yes");
    else puts("No");
    return 0;
}

B:小红的偶子串 


题目描述

小红定义偶串为:由很多长度为2的、两字符相同的字符串拼接而成的字符串。例如:"aabb","jjjjkk"是偶串,而"aaa","puup"则不是偶串。

小红拿到了一个仅由小写字母组成的字符串,她想取一段最长的连续子串为偶串。你能帮小红求出它的长度吗?

输入描述:

一个仅由小写字母组成的字符串,长度不超过200000。

输出描述:

最长的偶子串长度。

示例1

输入

aebbccdaa

输出

4

说明

"bbcc"为最长的偶子串。

解题思路:

我们发现整个字符串会被分为若干段,每一段的字母都是相同的,这样每一段就会对应一个字符数。

(1)字符数为偶数的段是可以作为某一符合要求的子串的中间段的

(2)字符数为奇数的段是不可以作为某一符合要求的字符串的中间段, 只能作为边界段

举个例子

s="ddaaabbcceeeff",我们对这个字符串分段,可以分为五段,每段长度为[2,3,2,2,3,2]

我们选择中间的bbcc作为中间段,左边有三个字符a,但是只能选俩个,右边有三个字符e,但是我们只能选俩个,也就是对于中间的偶数段我们可以全选,俩边的奇数段只能选他们的长度减1个。

需要注意对于中间段和左右俩侧奇数段并不一定都存在的,例如上述例子最左侧的ddaaa这个子串

我们选择ddaa是这部分符合要求的最长子段,中间段为dd,右侧奇数段为aaa,但是左侧奇数段不存在,我们将其长度记为0即可。

也就是说对于连续的偶数段我们可以全选,但是如果遇到奇数段,我们就不能全选了,然后就可以进行切割了,根据奇数段就可以切割出若干符合要求的子段了,对所有这些符合要求的子段求一个长度的最大值就是答案。

cpp代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N=2e5+10;
int main()
{
    string s;
    cin>>s;
    //ans记录答案,cnt记录各个段的长度,sum记录各个符合要求的子段长度用于更新答案
    int n=s.size(),cnt=1,ans=0,sum=0;
    for(int i=1;i<n;i++)
    {
        if(s[i]==s[i-1])cnt++;
        else {
            if(cnt%2==0){  //遇上偶数段,记录到sum
                sum+=cnt;
                cnt=1;
            }else { //遇上奇数段,奇数段取cnt-1个位置加入这一段
                sum+=cnt-1;
                ans=max(ans,sum);//这一个符合要求的段的长度就是sum,更新答案
                sum=cnt-1; //这一奇数段还可以作为后面的符合要求的段的左侧奇数段,所以sum=cnt-1
                cnt=1;
            }
        }
    }
    //处理最后一段
    if(cnt%2==0){
        sum+=cnt;
        ans=max(ans,sum);
    }else {
        sum+=cnt-1;
        ans=max(ans,sum);
    }
    cout<<ans<<endl;
    return 0;
}

C:小红的数组构造

题目描述

小红想让你构造一个长度为 n 的数组,满足以下三个条件:
1. 该数组最大值不超过 k。
2. 该数组所有数都不相同。
3. 数组所有数之和等于 x。

输入描述:

输入一行三个正整数 n,k,用空格隔开。
所有测试点满足1<=n<=1e5
所有测试点满足1≤k≤x≤1e14

输出描述:

如果无法构造,请输出-1。
否则输出 n 个正整数,用空格隔开,代表构造的数组。有多解时输出任意即可。

示例1

输入

4 6 15

输出

1 3 6 5

示例2

输入

2 2 2

输出

-1

说明

显然无法构造出两个不相等的正整数和为2。

解题思路:

首先我们先看一下不能构造的情况

(1)如果n>k,由于要构造一个长为n的序列,还要所有数都不同,最大值不能超过k,最多只能选出k个数,所以肯定构造不出来。

(2)最大值为k,如果选中所有的数就是[1,2,...,k],那么和为k*(k*1)/2,如果k*(k+1)/2<x,那么也无法构造出,这个还需要注意的一点就是这个k最大可以到1e14,那么K*(k+1)/2就会爆long long,但是我们可以发现当k>1e8的时候k*(k+1)/2必然大于x(1e14),所以我们只需要判断k<1e8的时候即可,就可以避免爆long long对答案产生影响。

上面是最明显俩种无解的情况

然后我们考虑构造

我们要构造一个长为n,并且最大值不能超过k,每个数都要不同,我们考虑一种可能的构造情况

我们可以构造一个长为n的连续的序列,例如[1,2,3,...,n],[2,3,4,5,...,n+1]都是长为n的连续的序列

这样长度是满足了,还要满足总和为x,最大值不超过k,对于这样的连续段越往左和越小,越往右和越大,所以具有二段性,我们可以二分这种连续段的最后一个值(最大值),就可以找到和大于等于x的和最小的那一段,由于此时这段和可能大于x,所以还要把多余的减掉才行,由于这一段是满足要求的最左边的那一段了,那么说明当前段的左边那段的和是小于x的,例如当前二分找到的段是[2,3,...,n+1],那么他左边那段就是[1,2,...n],相邻俩段相差就是n,也就是说多的那部分最多不超过n,要减去多的那部分,假设多了t(t<=n),只需要对当前二分到的这一段最小的t个元素每一个都减1即可。

那么到现在这一题做完了吗,并没有,我们可以发现如果找到的这一段为[1,2,3,4,...,n],此时还需要减去多余的部分t,如果t>0,就会把1减到0,那么这种情况就是无解的情况,也就是无法构造的情况,所以构造完答案并且减去多余的部分t之后,还要判断答案数组ans里面是否有元素小于等于0,如果有小于等于0的元素,说明无解,无法构造。

cpp代码如下

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
typedef long long LL;

const int N=1e5+10;

int n;
LL k,x;
int main()
{
    cin>>n>>k>>x;
    if(k<n){  //无解
        puts("-1");
        return 0;
    }
    
    if(k<1e8){  //无解
        if(k*(k+1)/2<x){
            puts("-1");
            return 0;
        }
    }
    LL l=1,r=k;  //二分和大于等于x的和最小的那段
    while(l<r){
        LL mid=l+r>>1;
        if(n*(mid+mid+n-1)/2>=x)r=mid;
        else l=mid+1;
    }
    
    LL diff=n*(r+r+n-1)/2-x;  //多余的部分t
    vector<LL>ans;
    for(LL i=r;i<=r+n-1;i++)  //记录答案
        ans.push_back(i);
    for(int i=0;i<diff;i++)  //减去多余的部分
        ans[i]--;
    for(int i=0;i<ans.size();i++) //看是否有小于等于x的元素
        if(ans[i]<=0){
            puts("-1");
            return 0;
        }
    for(int i=0;i<ans.size();i++)  //输出答案
        printf("%lld ",ans[i]);
    
    return 0;
}

D:小红的图上删边

题目描述

小红拿到了一个n个节点、m条边的无向连通图,每个节点的权值已知。
小红删掉一条边时,可以获得连接该边的两个节点“权值乘积末尾0数量”的价值。例如,一条边连接的两个点权值是50和60,那么小红删掉这条边获得的价值为3。
小红想知道,在保证这张图连通的情况下,最多可以通过删边获得多少价值?

输入描述:

第一行输入两个正整数 n 和 m,代表图的点数和边数。
第二行输入 n 个正整数 ai,代表每个点的权值。
接下来的 m 行,每行输入两个正整数 u 和 v,代表点 u 和点 v 有一条边连接。
保证图连通,且无重边,无自环。
所有测试点满足2≤n≤1e5
所有测试点满足n−1≤m≤min(1e5,n∗(n−1)/2)
所有测试点满足1≤ai≤1e14
所有测试点满足1≤u,v≤n

输出描述:

一个整数,代表删边可以获得的最大价值。

示例1

输入

3 3
5 8 25
1 2
2 3
1 3

输出

2

说明

解题思路:

首先我们考虑边权权值,题目说了边的权值就是边连接的俩个点权值乘积末尾0的数量,末尾0的数量是只和质因子2和质因子5有关的,因为10=2*5,所以说一个质因子2和一个质因子5就可以得到一个末尾0,所以我们对于边的权值只需要知道边连接的俩个点的权值所包含的因子2和因子5的数目就行了,这个地方我们需要注意一点ai的值最大可以达到1e14,对于一条边如果先将这条边连接的俩个点的权值相乘再来求因子2和因子5的个数是会爆long long的,我们可以分别求因子2和因子5的个数在加起来就可以避免爆long long 了。

首先我们正向考虑删除哪些边可以保证不影响图的连通性并且删去的边的权值之和最大,正向考考虑有点麻烦,正难则反这个是很常用的,我们来反向考虑一下,要使得删去的边的权值和最大,就是要使得留下的边的权值和最小并且使得图是连通的,这句话是不是很熟悉呢,这不就是最小生成树吗,所以只需要先求一遍最小生成树,然后所有边的权值总和减去最小生成树边权和就是答案。 

cpp代码如下

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long LL;

const int N=2e5+10;

int n,m;
LL a[N];
int p[N];
struct Edges{
    int u,v,w;
}edges[N];

int get_val(LL x,LL y)  //这个函数用于计算边的权值
{
    int cnt2=0,cnt5=0;
    int sz2=0,sz5=0;
    while(x%2==0)cnt2++,x/=2;
    while(x%5==0)cnt5++,x/=5;
    while(y%2==0)sz2++,y/=2;
    while(y%5==0)sz5++,y/=5;
    return min(cnt2+sz2,cnt5+sz5);
}
int find(int x)  
{
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];
}
int Kruskal()  //Kruskal求最小生成树
{
    int res=0;
    for(int i=1;i<=n;i++)p[i]=i;
    sort(edges+1,edges+1+m,[&](Edges A,Edges B){
        return A.w<B.w;
    });
    for(int i=1;i<=m;i++)
    {
        int u=edges[i].u,v=edges[i].v,w=edges[i].w;
        int pu=find(u),pv=find(v);
        if(pu!=pv){
            res+=w;
            p[pu]=pv;
        }
    }
    return res;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    LL sum=0;  //sum计算所有边的权值和
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        int w=get_val(a[u],a[v]);
        edges[i]={u,v,w};
        sum+=w;
    }
    
    int t=Kruskal();
    cout<<sum-t<<endl;  //总边和减去最小生成树就是答案
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值