2017网络新生赛题解


硬币翻转

题目描述

在桌面上有一排硬币,共N枚,每一枚硬币均为正面朝上。现在要把所有的硬币翻转成反面朝上,规则是每次可翻转任意N-1枚硬币(正面向上的被翻转为反面向上,反之亦然)。求一个最短的操作序列(将每次翻转N-1枚硬币成为一次操作)。

输入输出格式

输入格式:
输入只有一行,包含一个自然数N(N为不大于100的偶数)。

输出格式:
输出文件的第一行包含一个整数S,表示最少需要的操作次数。接下来的S行每行分别表示每次操作后桌上硬币的状态(一行包含N个整数(0或1),表示每个硬币的状态:0――正面向上,和1――反面向上,不允许出现多余空格)。

对于有多种操作方案的情况,则只需字典序最小输出一种。

输入输出样例
输入样例#1:
4
输出样例#1:
4
0111
1100
0001
1111


题目分析:
翻n-1枚硬币,就是有一枚不翻,也可以理解为只翻一枚,
如果是每次只翻一枚:要想把所有硬币翻转,即每次翻第i个,n次之后,硬币就全部翻转;
如果是每次翻n-1枚:即每次翻转时第i个不用翻,其他都翻,n次之后,硬币也全部翻转;
时间复杂度:O(n^2);

#include <stdio.h>

int main()
{
    int x,y,z,t,n;
    int a[100]={0};
    scanf("%d",&n);
    printf("%d\n",n);
    for(x=1;x<=n;x++)
    {
        for(y=1;y<=n;y++)
        {
            if(x!=y)
            {
                a[y] = !a[y];               
            }
            printf("%d",a[y]);
        }
        printf("\n");
    }

    return 0;
} 


约瑟夫问题

题目描述

n个人站成一圈,从某个人开始数数,每次数到m的人就被杀掉,然后下一个人重新开始数,直到最后只剩一个人。现在有一圈人,k个好人站在一起,k个坏人站在一起。从第一个好人开始数数。你要确定一个最小的m,使得在第一个好人被杀死前,k个坏人先被杀死。

输入输出格式

输入格式:
一个k,0


题目分析:
由题意可以知道,k个好人在前,k个好人在后。
我们假设他们站成一排,好人编号1 - k,坏人编号k+1 - 2k 模拟,假设数到m杀一个,剩余n个人,从第i号人开始数数,下一个杀掉的是第i+m-1个人。
要保证没有杀了好人,只要杀的人的编号>k就不是好人

当k>7的时候,m会远远大于k,所以要进行优化,减少时间复杂度.
当m>n的时候,数m次其实和数m%n次的结果是一样的,所以在每杀死一个坏人时,我们要更新要数的次数m
#include <stdio.h>       
int x,peo,m,mm;                        ///x即题中给的,peo就是2倍x,总人数,m是答案
int p,i,tm;                           ///p位置变量position,i在本题中计数,tm是杀人次数time。因为次数很确定,杀一半的人,所以运行到x就停止
bool b[30],god,kg;            ///b判断人死了没,god判断这个m满不满足,不满足就会保持true,kg是kill good people,判断是否杀了好人
int main()
{
    scanf("%d",&x);
    peo=2*x;
    m=x+1;god=true;         ///m至少是x+1
    while(god)
    {
        for(p=1;p<=peo;p++) 
        {
            b[p]=true;
        }
        tm=1;
        kg=true;
        p=1;      
        while(tm<=x && kg) ///次数到了,或者杀了好人,都跳出循环
        {
            i=0; ///i记次数
            mm = (m-1)%(peo-tm+1)+1;//不用重复循环 
            while(i<=mm)
            {
                if(b[p]==true) ///只有这个人还没死,i才会增加,数到了就准备杀了这个人,并跳出循环
                {
                    i++;    
                }
                if(i==mm) 
                break;       


                p++;
                if(p>peo) 
                {
                    p=p-peo;
                }

            }
            if(p<=x) ///如果这个人是好人,那就不用再杀人了,准备跳出循环等着m++
            {
                kg=false;
            }       
            else ///如果是坏人,让他死掉,然后p挪到下一个活着的人身上
            {
                b[p]=false;
                p++;
                if(p>peo) 
                p=p-peo;
            }                    
            tm++;
        }
        if(kg==true) 
            god=false;      ///如果tm达到x但是还没杀好人,那这事就成了,不用循环下去了
        else m++;
    }
    printf("%d\n",m);
    return 0;
}


装箱问题

题目描述

有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30,每个物品有一个体积(正整数)。

要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

输入输出格式

输入格式:
一个整数,表示箱子容量

一个整数,表示有n个物品

接下来n行,分别表示这n 个物品的各自体积

输出格式:
一个整数,表示箱子剩余空间。

输入输出样例
输入样例#1:
24
6
8
3
12
7
9
7
输出样例#1:
0


题目分析:
这是一个简单的动态规划问题:01背包.不过这个01背包只有重量而没有价值,所以我们可以把重量当成价值.
状态转移方程为f[j]=max(f[j],f[j-vi[i]]+vi[i]);
时间复杂度:O(V * n)

#include <stdio.h>

int max(int x,int y)
{
    if(x>y)
    return x;
    return y;
}

int v,n,i,j,f[20005],vi[35];
int main()
{
    scanf("%d%d",&v,&n);
    for(i=0;i<n;i++)
    {
        scanf("%d",&vi[i]);
    }
    for(i=0;i<n;i++)
    {
        for(j=v;j>=vi[i];j--)
        {
            for(j=v;j>=vi[i];j--)
             f[j]=max(f[j],f[j-vi[i]]+vi[i]);
        }
    }
    printf("%d\n",v-f[v]);
    return 0;
}


公路维修问题

题目描述

由于长期没有得到维修,A国的高速公路上出现了N个坑。为了尽快填补好这N个坑,A国决定对M处地段采取交通管制。为了求解方便,假设A国的高速公路只有一条,而且是笔直的。现在给出N个坑的位置,请你计算,最少要对多远的路段实施交通管制?

输入输出格式

输入格式:
输入数据共两行,第一行为两个正整数N、M (2<=N<=15000,M<=N)。第二行给出了N个坑的坐标(坐标值均在长整范围内,按从小到大的顺序给出,且不会有两个点坐标相同)。

输出格式:
仅一行,为最小长度和。

输入输出样例

输入样例#1:
18 4
3 4 6 8 14 15 16 17 21 25 26 27 30 31 40 41 42 43
输出样例#1:
25


题目分析:
先求出每个坑之间的距离,排除坑之间距离最大的m-1条路段,把剩下的路径的长度相加,并加上m就是最短的交通管制路段

#include <cstdio>
#include <algorithm>
#define Size 20005
using namespace std;
int main()
{
    int a[Size],b[Size],x,y,z,t,n,m;
    scanf("%d%d",&n,&m);
    for(x=0;x<n;x++)
    {
        scanf("%d",&a[x]);
        if(x!=0)
        {
            b[x-1] = a[x]-a[x-1];
        }
    }
    sort(b,b+n);
    int num = 0;
    for(x=0;x<n-m+1;x++)
    {
        num += b[x];
    }
    printf("%d\n",num+m);
    return 0;
}


A-B数对

题目描述

给出N 个从小到大排好序的整数,一个差值C,要求在这N个整数中找两个数A 和B,使得A-B=C,问这样的方案有多少种?

例如:N=5,C=2,5 个整数是:2 2 4 8 10。答案是3。具体方案:第3 个数减第1 个数;第3 个数减第2 个数;第5 个数减第4 个数。

输入输出格式

输入格式:
第一行2 个正整数:N,C。

第二行N 个整数:已经有序。注意:可能有相同的。
C 的范围是[1…1,000,000,000],N 的范围是[1…100,000],N 个整数中每个数的范围是:[0…1,000,000,000]

输出格式:
一个整数,表示该串数中包含的所有满足A-B=C 的数对的方案数。

输入输出样例

输入样例#1:
4 1
1 1 2 2
输出样例#1:
4


题目分析:
这道题要用逆向思维,对于数组的每个元素,我们可以把它当做B,已知C,在数组中找B+C的值,但如果我们每次都从当前位置开始遍历,那么肯定会超时,所以我们要用二分查找来找这个数,而这个数可能不止一个,所以我们要在找到的位置的左右进行遍历,找到所有的A.
时间复杂度O(NlogN)

#include <stdio.h>

int a[100005];

int Search(int *a,int target,int low,int high)
{
    while(low <= high)
    {
        int mid = (low + high)/2;
        if (a[mid] > target)
            high = mid - 1;
        else if (a[mid] < target)
            low = mid + 1;
        else //找到 
            return mid;
    }
    return -1;  
}


int main()
{
    int x,y,z,t,n,c,num;
    scanf("%d%d",&n,&c);
    for(x=0;x<n;x++)
    {
        scanf("%d",&a[x]);
    }
    num = 0;
    for(x=0;x<n;x++)
    {
        t = a[x]+c;
        int num1 = 1;//计数a[x]的个数 
        int num2 = 1;//计数a[x]+c的个数
        int flag = Search(a,t,0,n-1);//找出a[x]+c的位置 
        if(flag!=-1)//如果存在a[x]+c 
        {   
            y = x+1;
            while(a[y]==a[x])//找出有多少个a[x]相同的数 
            {
                num1++;
                y++;
            }
            x = y-1;//下标后移 
            y = 1;
            z = 1;
            while(z)//找出a[flag]附件有多少个相同的数 
            {
                z = 0;
                if(a[flag+y]==a[flag])
                {
                    num2++;
                    z = 1;
                }
                if(a[flag-y]==a[flag])
                {
                    num2++;
                    z = 1;
                }
                y++;
            }
            num += num1*num2;
        }

    }

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

    return 0;
}


回文字串

题目描述

回文词是一种对称的字符串。任意给定一个字符串,通过插入若干字符,都可以变成回文词。此题的任务是,求出将给定字符串变成回文词所需要插入的最少字符数。

比如 “Ab3bd”插入2个字符后可以变成回文词“dAb3bAd”或“Adb3bdA”,但是插入少于2个的字符无法变成回文词。

注:此问题区分大小写

输入输出格式

输入格式:
一个字符串(0

题目分析:
该题说是考察如何将一个字符串添加成一个回文串的,不如说是一道求最长公共自序列的变式题,为啥这么说呢?肯定是有原因在里面的
首先,我们要摸清回文串的特性,回文就是正着读反着读一样,一种非常对称的字符串;这就是我们的突破口。。这样我们就可以再建一个字符数组存储倒序的字符串

我们先分析下样例:ab3bd,
它的倒序是:db3ba
这样我们就可以把问题转化成了求最长公共自序列的问题,为啥可以这么转化呢?
它可以这么理解,正序与倒序“公共”的部分就是我们回文的部分,如果把正序与倒序公共的部分减去你就会惊奇的发现剩余的字符就是你所要添加的字符,也就是所求的正解
把不回文的加起来就是我们梦寐以求的东西:回文串
把ad,da加起来成回文串就是adb3bda,所以这个问题就可以转化成了求最长公共自序列的问题,将字符串的长度减去它本身的“回文的”(最长公共自序列)字符便是正解

找到解题思路后我们就可以开始写了,最长公共自序列问题是个经典的dp问题,
时间复杂度:O(n^2)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
using namespace std;
int n;
int dp[5001][5001];
char str1[5001],str2[5001];
int main()
{
    scanf("%s", str1+1);
    n = strlen(str1+1);
    for(int i = 1; i <= n; i++)
        str2[i] = str1[n-i+1];      //做一个逆序的字符串数组 
    for(int i = 1; i<=n; i++)
        for(int j = 1; j <= n; j++)
            if(str1[i] == str2[j])
                dp[i][j] = dp[i-1][j-1] + 1;        //最长公共自序列匹配 
            else
                dp[i][j] = max(dp[i-1][j], dp[i][j-1]);        //不匹配的往下匹配状态 
    printf("%d\n", n-dp[n][n]);                        //字符串长度减去匹配出的最长公共自序列的值 
    return 0;             //即需要添加的字符数 
}


the count of M

题目描述
有n个整数(1<=n<=200000),分别为a1, a2, a3, …, an。对于第i个整数ai,它的范围为0~10^9, 有k个整数(1<=k<=200000),分别为M1, M2, M3, …, Mk。对于第i个整数Mi,它的范围为0~10^9。你的任务是对M1, M2, M3, …, Mk这k个整数分别查找它们在a1, a2, a3, …., an这个序列里出现的次数。

输入描述
有多组数据。每组数据3行。结束标志为EOF。

第一行输入为两个整数n, k(1<=n, k<=200000)。N为整数的个数,k为数据的组数。

第二行输入为n个整数,分别为a1, a2, a3, …, an。对于第i个整数ai,它的范围为0~10^9。整数之间间隔为一个空格。

第三行为k个整数。分别为M1, M2, M3, …, Mk。对于第i个整数Mi,它的范围为0~10^9。整数之间间隔为一个空格。

每一个数据文件有多组数据,但保证每一个数据文件里的每组数据的n的和不超过200000,每一个数据文件里的每组数据的k的和不超过200000。

比如下面的样例输入,出现了两个n和k,每个n和k的范围都在1~200000以内且两个n的和不超过200000,两个k的和也不超过200000。

输出描述
对于每组数据输出一行。对于每个Mi,输出在a1, a2, a3, …, an这个序列里出现的次数。数与数之间需要间隔一个空格。注意这行最后不能出现空格。

样本输入

5 5
1 2 3 4 5
3 5 2 4 1
10 10
656 649 1646 1322 123 649 3201 9850 1245 3200
666 110 1000 649 9850 121 123 9850 1244 1646
样本输出
1 1 1 1 1
0 0 0 2 1 0 1 1 0 1

题目分析:

方法一,qsort排序+二分查找。对n个整数用qsort/sort进行排序,
第一次找等于M的第一个数的下标,第二次找大于M的第一个数的下标。
两下标相减为解。若第一次查找找不到M,则输出0。
时间复杂度为: O((n + k) * logn)
方法二, 两次qsort排序+遍历两个数组。
然后根据M的值对id数组进行排序,然后遍历两个数组存储答案。
时间复杂度为: O(nlogn + klogk)

参考代码1: qsort + 二分查找

    #include <stdlib.h>
    #include <stdio.h>
    int a[200005], n, k;
    //二分查找,找到第一个等于m的值的下标
    int LowerBinarySearch(int key) {
        int lo = 0, hi = n - 1;
        if (key > a[hi] || key < a[lo])
            return -1;
        while (lo < hi) {
            int mid = (lo + hi) / 2;
            if (a[mid] < key) lo = mid + 1;
            else if (a[mid] > key) hi = mid - 1;
            else hi = mid;
        }
        if (a[lo] == key)
            return lo;
        return -1;
    }
    //找到第一个比M大的数的下标
    int UpperBinarySearch(int key) {
        int lo = 0, hi = n - 1;
        if (a[hi] == key)//如果key为最后一个数,直接返回n
            return n;
        while (lo < hi) {
            int mid = (lo + hi) / 2;
            if (a[mid] <= key) lo = mid + 1;
            else if (a[mid] > key) hi = mid;
        }
        return lo;
    }
    //qsort的判断排序顺序函数
    int cmp(const void * p, const void * q) {
        return *(int *)p - *(int *)q;
    }
    int main() {
        while (~scanf("%d%d", &n, &k)) {
            for (int i = 0; i < n; i++)
                scanf("%d", &a[i]);
            qsort(a, n, sizeof(int), cmp);//先把数组进行升序排序
            for (int i = 0; i < k; i++) {
                if (i) putchar(' ');
                int tmp;
                scanf("%d", &tmp);
                int f1 = LowerBinarySearch(tmp);
                if (f1 == -1) putchar('0');//如果没有找到这个数,直接输出0
                else printf("%d", UpperBinarySearch(tmp) - f1);
                //找到第一个比M大的数,将其减去第一个M的下标,就可以得到有多少个M
            }
            printf("\n");
        }
        return 0;
    }

参考代码2: 两次qsort排序 + 遍历两个数组

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define maxn 200005
    int a[maxn], m[maxn], ans[maxn], id[maxn], n, k;
    int cmp1(const void * p, const void * q) {
        return *(int *)p - *(int *)q;
    }
    int cmp2(const void * p, const void * q) {
        return m[*(int *)p] - m[*(int *)q];
    }
    int main() {
        while (~scanf("%d%d", &n, &k)) {
            memset(ans, 0, sizeof(int) * maxn);
            for (int i = 0; i < n; i++)
                scanf("%d", &a[i]);
            for (int i = 0; i < k; i++) {
                id[i] = i;
                scanf("%d", &m[i]);
            }
            qsort(a, n, sizeof(int), cmp1);
            qsort(id, k, sizeof(int), cmp2);
            int p = 0, q = 0;
            while (p < n && q < k) {
                int x = id[q];
                if (a[p] == m[x]) {
                    ans[x]++;
                    while (++p < n && a[p] == m[x])
                        ans[x]++;
                    while (++q < k && m[id[q]] == m[x])
                        ans[id[q]] = ans[x];
                }
                else if (a[p] > m[x]) q++;
                else p++;
            }
            for (int i = 0; i < k; i++) {
                if (i) putchar(' ');
                printf("%d", ans[i]);
            }
            printf("\n");
        }
        return 0;
    }


A - B

题目描述:

高精度减法,输出a-b(保证不含前导0)

输入描述:

多组数据。每组数据占一行,每行两个整数a, b,输入以EOF结束。

注: 0 <= a, b <= 10^10000, 保证a, b都不含前导0。

输出描述:

对于每个数据,输出一行,该行仅含一个整数,为结果(是负数要输出负号)。

样例输入:

2 1
9 4

样例输出:

1
5

题目分析:

简单高精度题。时间复杂度: O(max{loga, logb})

#include <stdio.h>
    #include <string.h>
    #define maxn 10005
    int ans[maxn], A[maxn], B[maxn];
    char s1[maxn], s2[maxn];
    int max(int a, int b) {
        return a > b ? a : b;
    }
    //模拟减法的过程
    void minus(int * a, int * b, int n) {
        int flag = 0;//flag是判断其上一位是否借位,如果上一位借位,则这一位要减一
        for (int i = 0; i < n; i++) {
            ans[i] = flag + a[i] - b[i];
            if (ans[i] < 0) { ans[i] += 10; flag = -1; }//当被减之后小于零,则需要向下一位借一位
            else flag = 0;
        }
    }
    int main() {
        while (~scanf("%s%s", s1, s2)) {
            int m = strlen(s1), n = strlen(s2);
            memset(A, 0, sizeof(A));
            memset(B, 0, sizeof(B));
            int maxi = max(m, n);//找到较大数组的长度
            //将字符串变为整型数组,并在较短的数组后加 0 ,好进行计算,即将两个数组变为同样的长度
            for (int i = 0; i < maxi; i++) {
                A[i] = (m > i) ? s1[m - 1 - i] - '0' : 0;//如果m>i,表明A数组还有数字,将其变为整型,如果m<i则表示,A已经被减完了,但B数组还有数字,所以要在其后面加0
                B[i] = (n > i) ? s2[n - 1 - i] - '0' : 0;//同上
            }
            //当被减数小于减数时要输出'-'
            if (m < n || (m == n && strcmp(s1, s2) < 0)) {
                putchar('-'); minus(B, A, maxi);
            }
            else minus(A, B, maxi);
            int len = maxi - 1;
            //把数字的前导零给去掉,如00152,只输出152
            while (len > 0 && !ans[len])
                len--;
            for (int i = len; i >= 0; i--)
                printf("%d", ans[i]);
            printf("\n");
        }
        return 0;
    }


The Sum of Prime Factor

题目描述:

一个除1以外的正整数都能被分解为若干个质因子的乘积。比如24 = 2 * 2 * 2 * 3。现在给你m, n, 求[m, n]内的所有正整数被分解为质因子的个数总和。

例如 : m = 5, n = 9时 :
5 = 5         --- 1
6 = 2 * 3      --- 2
7 = 7         --- 1
8 = 2 * 2 * 2   --- 3
9 = 3 * 3      --- 2

所以[5, 9]内的所有正整数被分解为质因子的个数总和为1+2+1+3+2=9个。

输入描述:

输入为多组数据。第一行为一个整数t(1<=t<=100000), 代表数据的组数。紧接着为t行,每行有2个数m, n(2<=m<=n<=10000000), m, n之间间隔一个空格。

输出描述:

对于每个数据,输出一行,这行仅含一个整数,为[m, n]内的所有正整数被分解为质因子的个数总和。

样例输入:

2
5 9
11 20

样例输出:

9
21

题目分析:
这道题是这次最有意思的题了吧。
大部分人的想法是用普通方法判断x是否为素数。如果是,它就只能分解成一个质因子。否则,令y = x除x的最小质因子的商,则x能分解成y能分解成质因子的个数+1。然而这种算法的复杂度为: O(n*√n)。因为时间复杂度太大这种方法会超时。所以可以首先通过筛选法筛选素数,之后利用筛选法求出答案。时间复杂度为: O(n*logn)。
因为t最大为100000,所以需要多一个递推来计算2-x共有几个质因子。代码如下:
for (int i = 3; i <= maxn; i++)
num[i] += num[i - 1];
这样,我们可以通过num[n] - num[m - 1]输出答案。

#include <stdio.h>
    #define maxn 10000000
    int num[maxn + 5];
    int main() {
        num[2] = 1;  
        for (int i = 3; i < maxn; i += 2)
            num[i] = 1;
        //筛选出素数    
        for (int i = 3; i * i < maxn; i += 2)
        {
            if (num[i])//将素数的倍数标零
                for (int j = i; i * j < maxn; j += 2)
                    num[i * j] = 0;
        }          
        //找出每个数的素因子的个数
        for (int i = 2; i * i < maxn; i++)
            if (num[i] == 1)
                for (int j = i; i * j <= maxn; j++)
                    if (num[j] && !num[i * j]) num[i * j] = num[j] + 1;//当num[i]是素因子时,num[j]的素因子个数已经求来,
//num[i*j]没有算过,其积的素因子个数为num[j]的素因子个数加一,这里可以自己用具体的数来推算一下

        //递推出每个数之前有多少个素因子
        for (int i = 3; i <= maxn; i++)
            num[i] += num[i - 1];
        int m, n, t;
        scanf("%d", &t);
        while (t--) {
            scanf("%d%d", &m, &n);
            printf("%d\n", num[n] - num[m - 1]);
        }
        return 0;
    }

可以比较一下下面这种做法所用的时间:

    #include <stdio.h>
    #define maxn 10000000
    int s[maxn + 5];
    int main() {
        s[2] = 1;
        for (int i = 3; i < maxn; i += 2)
            s[i] = 1;
        for (int i = 4; i < maxn; i++)
            for (int j = 2; j * j <= i; j++)
                if (0 == i % j) { s[i] = s[i / j] + 1; break; }
        for (int i = 3; i < maxn; i++)
            s[i] += s[i - 1];
        int m, n, t;
        scanf("%d", &t);
        while (t--) {
            scanf("%d%d", &m, &n);
            printf("%d\n", s[n] - s[m - 1]);
        }
        return 0;
    }

当然了线性筛选更快。时间复杂度: O(n)
//将筛选方法改为欧拉筛法,减少时间复杂度.
    #include <stdio.h>
    #define maxn 10000000
    int num[maxn + 5], prime[664600];
    int main() {
        int cnt = 0, i, j;
        num[2] = 1;
        for (i = 3; i < maxn; i += 2)
            num[i] = 1;
        for (i = 2; i < maxn; i++) {
            if (1 == num[i]) prime[cnt++] = i;
            for (j = 0; j < cnt && i * prime[j] <= maxn; j++) {
                num[i * prime[j]] = num[i] + 1;
                if (0 == i % prime[j])
                    break;
            }
        }
        for (i = 3; i <= maxn; ++i)
            num[i] += num[i - 1];
        int m, n, t;
        scanf("%d", &t);
        while (t--) {
            scanf("%d%d", &m, &n);
            printf("%d\n", num[n] - num[m - 1]);
        }
        return 0;
    }

猜你喜欢

转载自blog.csdn.net/qecode/article/details/78525831
今日推荐