NENU SUMMER TRAINING #1 (题解)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ShadowGhostH/article/details/81157054

A

题意:定义一个填充块,填充块由 ‘o’, ‘g’ 交替出现组成,并且以 ‘o’ 开头,以 ‘o’ 结尾,形如:”ogo”, “ogogo”, “ogogogo”等,现在给出一个给定长度的字符串,需要将其中所有的填充块换为 ‘***’ 之后输出。

思路:问题的核心在于如何找出所有的填充块,我们可以发现,填充块可以转化成,以o开头与不少于一组go相连的连续字符串。只需要找到符合要求的字符串,然后尽可能向后扩展就可以了,将这部分缩为一组 ‘***’ 。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 105;
char a[maxn], b[maxn];

int main()
{
    int lena, pa=0, pb=0;                                //pa用来遍历a字符串,pb用来往b字符串中添加
    scanf("%d%s", &lena, a);

    while(pa != lena){                                   //当遍历到结尾时结束
        if(a[pa]=='o' && a[pa+1]=='g' && a[pa+2]=='o'){  //找出一个匹配的开头
            b[pb] = b[pb+1] = b[pb+2] = '*';             //置*
            pa += 3;
            pb += 3;
            while(a[pa]=='g' && a[pa+1]=='o'){           //找出之后匹配的循环节,直接跳过
                pa += 2;
            }
        }

        else b[pb++] = a[pa++];                          //如果不是的话,直接添加到b字符串
    }

    b[pb] = '\0';                                        //标志字符串结尾
    printf("%s\n", b);
    return 0;
}

B

题意:有n*m大小的舞台,若干的演员在台上固定的位置,定义好的方向(x, y,d)满足在(x, y)位置上没有演员,并沿着d方向可以看到演员(可以根据样例理解)。例如1 0 1用1表示演员,则在(1, 2)这个位置可以从左右两个方向看到演员,所以好的方向的总数为2。现在给出舞台大小和演员位置,求好的方向的总数。

思路:这里给出一种模拟的解法。我们遍历所有位置,对有演员的位置进行check,chek思路为,从当前位置沿上下左右四个方向进行查找,遇到空的位置cnt++,遇到有演员或者边界则停止查找。这样的好处是,每一个位置最多被从上下左右四个方向查找到四次,为常数级别,那么复杂度就是O(n2) 可以通过。同时也可以通过前缀和的思想,或者枚举方向进行求解,方法不唯一。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1005;
int mp[maxn][maxn];
int n, m, cnt=0;

void check(int i, int j)
{
    for(int x=i-1; x>=0; x--){      //向上查找
        if(mp[x][j] == 0) cnt++;    //空位置计数++
        else break;                 //有演员则跳出
    }
    for(int x=i+1; x<n; x++){       //向下查找
        if(mp[x][j] == 0) cnt++;
        else break;
    }
    for(int y=j-1; y>=0; y--){      //向左查找
        if(mp[i][y] == 0) cnt++;
        else break;
    }
    for(int y=j+1; y<m; y++){       //向右查找
        if(mp[i][y] == 0) cnt++;
        else break;
    }
}

int main()
{
    cnt = 0;
    scanf("%d%d", &n, &m);
    for(int i=0; i<n; i++)
        for(int j=0; j<m; j++)
            scanf("%d", &mp[i][j]);

    for(int i=0; i<n; i++)
        for(int j=0; j<m; j++)
            if(mp[i][j] == 1)
                check(i, j);

    printf("%d\n", cnt);

    return 0;
}

C

题意:在一个坐标轴上需要从0点到s点(假设每个坐标点之间间距为1km),需要耗时不超过t。现在有n辆车,每辆车有两个属性,租金和油箱容量。开车有两种方式,1分钟 1千米 耗2单位油,2分钟 1千米 耗1单位油。而在(0, s)之间有若干加油站,每次可以无耗时无花费地把油加满。问从0到s点的最小花费,无解输出-1。

思路:本题可以转化为 → 找到一个最小的、满足题意的油箱容量 → 然后在满足容量的所有车中,找到花费最小的。这两步中第二步很好解决,一个遍历就完成了。关键问题就在第一部分,如何确定一个尽可能小的,满足题意的油箱容量。满足题意的值最小,我们首先可以想到的是二分搜索(之后的训练中会讲到,正确性不在此证明)。而二分搜索需要较快的判断一个值是否满足条件。而我们可以知道,车通过每一段间距的时间,只和间距的长度有关,和顺序和位置都无关。现在证明一下如何快速判断一个邮箱容量是否满足条件。
对于一个油箱容量g,对于所有的间距d ,如果dg2,则这段耗时为d2。如果g2<dg,这部分耗时为(gd)1+(2dg)2。如果d>g 则无法到达。这是我们队每一段独立的间距进行的分析,而我们不能遍历地去求每一段间距对应的时间,复杂度太高了。我们可以把所有dg2的总长度求出来,时间就是总长度的一半。而对于 kg2<dg 的部分,我们求出总长度dk, 然后 这部分的耗时应该为(gkdk)1+(2dkgk)2 化简后为4dkgk
而我们将所有的间距进行排序之后,由于有序性,所有满足条件的部分一定是连续的。我们就可以用前缀和来维护区间和,那么之后的问题就是如何快速找到两种条件不同的分界的位置。用upper_bound() 有兴趣的可以下去自己查一下,这里不做讲解。
至此,所有问题解决。我们用二分容量,然后使用前缀和维护满足条件的间距和。快速判断是否满足条件。最后遍历找出最优解。需要注意的是,二分的时候需要判断是否会出现错误答案。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 2e5+5;
pair<int, int> car[maxn];
int p[maxn], dist[maxn], sum[maxn];
int n, k, s, t;

int found(int l, int r)
{
    while(l<=r){
        int mid = (l+r)/2;
        int pk = upper_bound(dist, dist+k+1, mid/2)-dist;    //前pk段1km/min
        int sumt = sum[pk] + (3*(s-sum[pk]) - mid*(k+1-pk)); //邮箱容量为mid时, 所花费的总时间
        if(sumt > t) l = mid+1;
        else r = mid-1;
    }

    int pk = upper_bound(dist, dist+k+1, l/2)-dist;           //答案检查, 防止异常结束
    int sumt = sum[pk] + (3*(s-sum[pk]) - l*(k+1-pk));
    if(sumt>t) return -1;
    return l;
}

int main()
{
    sum[0] = p[0] = 0;
    scanf("%d%d%d%d", &n, &k, &s, &t);

    for(int i=0; i<n; i++)
        scanf("%d%d", &car[i].first, &car[i].second);
    for(int i=1; i<=k; i++)
        scanf("%d", &p[i]);

    sort(p, p+k+1);
    for(int i=1; i<=k; i++)
        dist[i-1] = p[i]-p[i-1];
    dist[k] = s-p[k];
    sort(dist, dist+k+1);

    for(int i=1; i<=k+1; i++)         //处理前缀和
        sum[i] = sum[i-1] + dist[i-1];

    int mink = found(dist[k], 2*dist[k]+1);
    int ans = INT_MAX;

    for(int i=0; i<n; i++)
        if(car[i].second >= mink) ans = min(ans, car[i].first);

    if(ans==INT_MAX || mink==-1) puts("-1");
    else printf("%d\n", ans);
    return 0;
}

D

题意:已有n本书,已知买一本需要a元,两本b元,三本c元,问最少花多少钱,可以使已有的变为4的倍数。
思路:按照n模4的余数,分情况讨论,注意枚举所有情况。会爆int,记得开long long

#include<bits/stdc++.h>
using namespace std;

int main()
{
    long long n, a, b, c;
    scanf("%I64d%I64d%I64d%I64d", &n, &a, &b, &c);

    if(n%4==0) puts("0");
    else if(n%4==1) printf("%I64d\n", min(min(3*a, a+b), c));
    else if(n%4==2) printf("%I64d\n", min(min(2*a, b), 2*c));
    else if(n%4==3) printf("%I64d\n", min(min(a, b+c), 3*c));

    return 0;
}

E

题意:给出一个n长度的序列,和m段连续子序列,可以从这些子序列中选择任意多个进行求和,问如何使和最大。
思路:贪心的想法是我们将所有和为正数的子序列加和,即是我们想要的答案,而求连续区间的和,可以用前缀和的思路。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 105;
int n, m, a[maxn], sum[maxn];


int main()
{
    int ans = 0;
    sum[0] = 0;
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++){
        scanf("%d", &a[i]);
        sum[i] = sum[i-1]+a[i];
    }

    for(int i=0; i<m; i++){
        int l, r;
        scanf("%d%d", &l, &r);
        if(sum[r]-sum[l-1] > 0) ans+=sum[r]-sum[l-1];
    }

    printf("%d\n", ans);
    return 0;
}

F

题意:需要构造一个长度为n的序列,对这个序列有m次查询,每次查询给定一个连续的区间,查询的结果mex表示的是,不在查询序列中的,最小的数。构造这个序列,是的查询到的mex的最小值,尽可能的大。
思路:这是一个构造类型的题。我们很容易想到,让一个查询序列中的所有数从0开始排并且不重复,可以使得查询的mex最大。而我么可以证明,当我们找到了最小区间长度d,那么我们就构造一个[0,d) 循环的数列,即可满足题意。因为对于最小的查询区间,他的mex的值最大即为d ,而对于所有长度大于等于d 的区间,在他们之中一定会存在一个包含[0,d) 中所有数的子序列。而他的mex值不会小于d

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e5+5;

int main()
{
    int n, m, d=INT_MAX;
    scanf("%d%d", &n, &m);

    for(int i=0;i<m;i++){
        int l, r;
        scanf("%d%d", &l, &r);
        d = min(d, r-l+1);
    }

    printf("%d\n", d);
    for(int i=0; i<n; i++)
        printf("%d%c", i%d, i==n-1?'\n':' ');

    return 0;
}

G

题意:给定一个长度为n的字符串,从字母G开始,一次只能往左或往右跳正好k个单位长度,问是否能正好到达T。如果跳动的目标位置是#的话,则不能到达。
思路:从G到T还是从T到G只是相对位置,所以我们可以直接规定从下标小的位置跳到下标大的位置。for循环模拟跳动,到边界或者跳到#则输出不能,跳到目标位置则输出能。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e5+5;

int main()
{
    int n, k, pt, pg, can=0;
    char a[maxn];
    scanf("%d%d", &n, &k);
    scanf("%s", a);

    for(int i=0; i<n; i++){
        if(a[i]=='T') pt=i;
        if(a[i]=='G') pg=i;
    }
    if(pt > pg) swap(pt, pg);
    for(int i=pt; i<n; i+=k){
        if(a[i] == '#') break;
        if(i == pg) {
            can = 1;
            break;
        }
    }

    if(can) puts("YES");
    else puts("NO");
    return 0;
}

H

题意:从n个人当中,选取n1个为一组,n2个为另外一组,求两组平均值的和最大是多少?
思路:贪心的想法,首先可以明确,这n1+n2 个人的和,一定是所有可能中最大的。而对于这两组,最有钱的人应该在人少的那一组,使得总体的平均值最大。证明如下 首先由题意可得平均值的和为
sum1n1+sum2n2=n2sum1+n1sum2n1n2
假设 n1<n2,从 n1n2 中交换一人,使得交换后,sum1 减少了k, sum2 增加了k,即
sum1kn1+sum2+kn2=n2sum1+n1sum2+(n1n2)kn1n2
其中, n1<n2,所以平均值的和减小了。反证得,越有钱的人,应该在人少的那组,使得总体平均值最大。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5+5;

LL a[maxn];

int main()
{
    int n, n1, n2;
    LL sum1 = 0, sum2 = 0 ;
    scanf("%d%d%d", &n, &n1, &n2);

    for(int i=0; i<n; i++)
        scanf("%I64d", &a[i]);
    sort(a, a+n);
    if(n1>n2) swap(n1, n2);

    for(int i=0; i<n1; i++)
        sum1 += a[n-1-i];
    for(int i=0; i<n2; i++)
        sum2 += a[n-1-n1-i];

    printf("%f\n", (double)sum1/n1 + (double)sum2/n2);
    return 0;
}

I

题意:输掉一场即为淘汰,已胜x场的人,只能和与自己胜场相差不超过1的人对战,问n个人最多有人赢几场
思路:斐波那契数列,求可满足的最大位置

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5+5;

LL a[maxn];

int main()
{
    LL n;
    scanf("%I64d", &n);
    a[0] = 1;
    a[1] = 2;
    int p = 1;
    while(++p){
        a[p] = a[p-1] + a[p-2];
        if(a[p]>n) break;
    }

    printf("%d\n", p-1);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/ShadowGhostH/article/details/81157054
今日推荐