• 第14届蓝桥杯省赛 ---- C/C++ C组



    第14届蓝桥杯省赛真题,关于数论的题目不是很会,又没理解的很清楚,所以有两道的题解没有发出来。
    这两道题:互为 质数的个数公因数匹配

    1. 求和

    在这里插入图片描述
    这道题很简单的,直接对其进行求和就好了,可以使用循环求和,也可以利用那个等差数列的公式((s1 + sn) * n) / 2.
    在这里插入图片描述
    最后的答案就是204634714038436,但是如果用编程算的话记得得开long long.

    2. 工作时长

    在这里插入图片描述
    这道题是给我们一堆数据,表示一个人这一年中的所有工作时长,并且题目中已经要求其上班下班只打一次卡。
    所以这道题的思路是很简单的,只需要对数据进行排序,然后每两个进行一对处理,即可求出这次的共工作时长,然后将所有的求出来就好了。
    感觉难点在于读入数据很麻烦,需要注意一些细节。

    #include 
    #include  
    
    using namespace std;
    
    const int N = 530;
    
    struct Data
    {
    	int month,day;
    	int h,m,s;
    }d[N];
    
    bool cmp(Data a, Data b)
    {
    	if (a.month != b.month)
    		return a.month < b.month;
    	else if (a.day != b.day)
    		return a.day < b.day;
    	else if (a.h != b.h)
    		return a.h < b.h;
    	else if (a.m != b.m)
    		return a.m < b.m;
    	else
    		return a.s < b.s;
    }
    
    
    int main()
    {
    	//读入数据 
    	string s;
    	for (int i = 0; i < 520; i++)
    	{
    		getline(cin,s);//读入一行
    		//s.c_str(), 将string 形式的s 转化成 c语言中的char* 的形式。 
    		sscanf(s.c_str(),"2022-%d-%d %d:%d:%d",&d[i].month,&d[i].day,&d[i].h,&d[i].m,&d[i].s);	
    	}
    	
    	//将其进行排序 
    	sort(d, d + 520, cmp);
    	
    	//计算答案 
    	int ans = 0;
    	for (int i = 0; i < 520 - 1; i += 2)
    	{
    		//分别求出开始和结束的秒数,相减即可。
    		int st = d[i].day * 24 * 60 * 60 + d[i].h * 60 * 60 + d[i].m * 60 + d[i].s; 
    		int ed = d[i + 1].day * 24 * 60 * 60 + d[i + 1].h * 60 * 60 + d[i + 1].m * 60 + d[i + 1].s;
    		
    		ans += ed - st;
    	}
    	
    	printf("%d\n",ans);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    3. 三国游戏

    在这里插入图片描述
    在这里插入图片描述
    题目要求:
    输入一个整数n表示有n个事件发生,接下来的三行,每一行输入n个数,表示其第i个事件发生的时候当前自己的士兵增加多少,要求我们求出所有事件中,如果有人获胜,然后那个获胜的国家的最多发生的次数是多少。
    思路:

    • 我们假设第 i 个事件发生时候,x,y,z,w分别表示魏蜀吴已经第i个事件的价值
    • 魏国的价值是w[i] = x[i] - y[i] - z[i]
    • 蜀国的价值是w[i] = y[i] - x[i] - z[i]
    • 吴国的价值是w[i] = y[i] - x[i] - y[i]
    • 既然求出每个国家相对于每个事件的简直之后,对价值数组进行降序的排序,利用sum变量来记录当前的价值,如果价值大于0的话就是使事件加1,否则就直接break掉,至此之后的事件都是递减的了。
    • 最后分别比较魏蜀吴三个国家的最大值就好了。
    #include 
    
    using namespace std;
    
    const int N = 1e5 + 10;
    
    typedef long long LL;
    
    int n; //n 个事件 
    int X[N],Y[N],Z[N];	//对应魏蜀吴三个国家 
    int w[N];	//第i个事件的价值数组 
    
    bool cmp(int a, int b)
    {
    	return a > b;
    }
    
    
    int GetAns(int* a, int* b, int* c)
    {
    	//初始化价值数组 
    	memset(w, 0, sizeof w);
    	for (int i = 1; i <= n; i++)
    		w[i] += a[i] - b[i] - c[i];
    	//将其的价值按照从大到小的顺序排序 
    	sort(w + 1, w + 1 + n, cmp);
    	
    	LL sum = 0;
    	int ans = 0;
    	for (int i = 1; i <= n; i++)
    	{
    		//如果其中价值小于了0,就说明它不会胜利,又因为是从大到小排序的,所以之后的一定不会在胜利了。 
    		sum += w[i];
    		if (sum > 0)
    			ans++;
    		else
    			break; 
    	}
    	
    	if (ans == 0)
    		return -1;
    	else
    		return ans;
    	
    }
    
    int main()
    {
    	scanf("%d",&n);
    	
    	//读入数据 
    	for (int i = 1; i <= n; i++)
    		scanf("%d",&X[i]);
    	for (int i = 1; i <= n; i++)
    		scanf("%d",&Y[i]);
    	for (int i = 1; i <= n; i++)
    		scanf("%d",&Z[i]);
    	
    	//分别求出三个国家中,每个国家获胜的最多次数是多少,如果获胜不了,返回-1。 
    	int ans_A = GetAns(X,Y,Z);
    	int ans_B = GetAns(Y,Z,X);
    	int ans_C = GetAns(Z,X,Y);
    	
    	printf("%d\n",max(ans_A,max(ans_B,ans_C)));
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    4. 填充

    在这里插入图片描述
    题目要求:
    将输入的字符串其中的?换成0或者1,看看最多能组成多少个无重叠的连续00或者11.
    思路:

    • 先说一下自己的错误,下图中上面我是两两一对进行匹配,所以是不完全的,只要20个,而正确的则是有22个。

    在这里插入图片描述

    • 当我们在框选连续的00和11时候,只需要判断当前的是否是?如果是?一定可以和下一个满足,或者说这个和下一个本来就是相同的,再或者说后面的那个是?。
    • 所以只要满足这三种条件的话就是一组,并且使i跳过下一个。
    #include 
    
    using namespace std;
    
    const int N = 1e6 + 10;
    
    char s[N];
    
    int main()
    {
    	scanf("%s",s);
    	int len = strlen(s); 
    	int res = 0;
    	
    	for (int i = 0; i < len - 1; i++)
    	{
    		if (s[i] == s[i + 1] || s[i] == '?' || s[i + 1] == '?')
    		{
    			res ++;
    			i++;
    		}
    	}	
    	
    	printf("%d\n",res);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    5. 翻转

    在这里插入图片描述
    题目要求:
    题目要求输入字符串st,然后将s转化成t转化的要求满足s[i - 1] == s[i + 1]条件才可以转化s[i]
    思路:

    • 我们只需要对字符串s进行遍历,比较s[i] 和 s[j]如果两者不一致的时候,就去看看是否能替换,如果能替换那么替换次数加1,否则的话,直接返回-1就好了。
    #include 
    using namespace std;
    
    const int N = 1e6 + 10;
    
    int n;
    char s[N],t[N]; //t为主串,a为子串,将s翻转成t.
     
    int GetAns()
    {
    	int len = strlen(s);
    	int res = 0;
    	for (int i = 0; i < len; i++)
    	{
    		//如果说当前的字符不相等的情况下才去考虑是否翻转 
    		if (s[i] != t[i])
    		{
    			//首尾不可翻转 
    			if (i == 0 || i == len - 1)
    				return -1; 
    			else if (s[i - 1] != s[i + 1])  //前后不相同不可翻转
    				return -1;
                else    //可以翻转
                    res++;
    		}
    	}
    	
    	return res;
    }
    int main()
    {
    	scanf("%d",&n);
    	while (n--)
    	{
    		scanf("%s%s",s,t);
            if (strcmp(s,t) == 0)
                printf("0\n");
            else
            {
                int ans = GetAns();
                if (ans == 0)
                    printf("-1\n");
                else
                    printf("%d\n",ans);
            }
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    6. 子矩阵

    在这里插入图片描述
    题目要求:
    给我们一个n * m的矩阵,然后对矩阵中所有a * b的子矩阵进行求解最小值和最大值。将每一块矩阵的最小值和最大值的乘积相加在一起。
    暴力:
    这道题可以直接使用暴力来做,对于矩阵中每一个位置开始,向左,下延申矩阵,然后求出其的最大值和最小值,将其乘积加起来就好了,但是下面的做法只能过6个测试用例,感觉也还行,关键写起来也简单,比赛的时候,先考虑暴力做法吧,先把暴力的分拿了。
    在这里插入图片描述

    #include 
    
    using namespace std;
    
    const int N = 1010, MOD = 998244353;
    
    
    typedef long long LL;
    
    int n, m, A, B;
    int g[N][N];
    
    
    int main()
    {
    	//输入数据 
    	scanf("%d%d%d%d",&n,&m,&A,&B); 
    	for (int i = 0; i < n; i++)
    		for (int j = 0; j < m; j++)
    			scanf("%d",&g[i][j]);
    			
    	LL res = 0;
    	for (int i = 0; i < n; i++)
    		for (int j = 0; j < m; j++)
    		{
    			int mi,ma;
    			mi = ma = g[i][j];
    			for (int k = i; k < A + i; k++)
    				for (int l = j; l < B + j; l++)
    				{
    					mi = min(mi, g[k][l]);
    					ma = max(ma, g[k][l]);
    				}
    			res = (res + (LL)mi * ma) % MOD;
    		}
    	printf("%d\n",res);
    	return 0;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    二维滑动窗口:
    这一种方法则可以通过全部的测试用例

    • 首先先预处理出每一行的窗口为b的的最大值和最小值。
    • 接着在对每一列进行一个窗口大小为a最大值和最小值。
    • 然后就可以使两者相乘的和加起来就好了。
    #include 
    
    using namespace std;
    
    const int N = 1010, MOD = 998244353;
    
    typedef long long LL;
    
    int n, m, A, B;
    int g[N][N], rmax[N][N], rmin[N][N];
    int que[N];
    
    void get_max(int* a, int* b, int size, int k)
    {
        int front = 0, rear = 0;
        
        for (int i = 0; i < size; i++)
        {
            if (front != rear && que[front] <= i - k)   front++;
            
            while (front != rear && a[que[rear - 1]] <= a[i])   rear--;
            
            que[rear++] = i;
            
            if (i >= k - 1)
                b[i] = a[que[front]];
        }
    }
    
    void get_min(int* a, int* b, int size, int k)
    {
        int front = 0, rear = 0;
        for (int i = 0; i < size; i++)
        {
            if (front != rear && que[front] <= i - k)   front++;
            
            while (front != rear && a[que[rear - 1]] >= a[i])   rear--;
            
            que[rear++] = i;
            
            if (i >= k - 1)
                b[i] = a[que[front]];
        }
    }
    
    
    int main()
    {
        scanf("%d%d%d%d",&n, &m, &A, &B);
        
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                scanf("%d",&g[i][j]);
        
        //预处理每一行的最大值和最小值
        for (int i = 0; i < n; i++)
        {
            get_max(g[i], rmax[i], m, B);
            get_min(g[i], rmin[i], m, B);
        }
    
        int res = 0;
        int a[N],b[N],c[N]; //a临时存储A区间内的最大值或者最小值。 b存储矩阵中的最大值, c存储矩阵中的最小值。
        //i从0开始的是不会有值的。 
        for (int i = B - 1; i < m; i++)
        {
            for (int j = 0; j < n; j++)
                a[j] = rmax[j][i];
            
            get_max(a, b, n, A);
            
            for (int j = 0; j < n; j++)
                a[j] = rmin[j][i];
            
            get_min(a, c, n, A);
            //j与外层循环的i意思一致。
            for (int j = A - 1; j < n; j++)
                res = (res + (LL)b[j] * c[j]) % MOD;
        }
            
        printf("%d\n",res);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    8. 异或和之差

    在这里插入图片描述
    题目要求:
    题目要求我们求出给定的一个数组中,两个不相交子数组,的异或和之差,也就是所选的两个子数组相减,使这个差值变得最大,返回这个差值。
    思路:

    • 首先我们需要知道,要使数的差值最大,那么就需要选出一个异或值最大的数组,和一个一个异或值最小的数组。
    • 又因为要反复得计算异或值,同样的数可能被异或很多次,所以我们可以利用前缀和性质,同样求出前缀异或和,和后缀异或和两个数组,分别表示为ls[N]和rs[N].
    • 想象一下,前缀和的性质,s[l,r] = s[r] - s[l - 1] (l <= r)
    • 其实这个性质同样可以运用到异或和数组中去,s[l,r] = s[r] ^ s[l - 1].

    因为a ^ b = c 即可得出 b ^ c = a, a ^ c = b。
    所以也就能发现:s [l , r] = s[r] ^ s[l - 1]
    在这里插入图片描述

    • 那么对于数组s是一个前缀和数组,里面其实存放的是每一个区间的前缀异或和,我们对数组进行字典树的一个很经典的操作,就可以得出s[i] 异或那个数最大。
    • 既然可以求出1个i那么就可以求出所有的 i.
    • 下面就有点动态规划的意思了.
    • 状态表示:
    • 集合:rmax[i] 表示前i个异或和区间
    • 属性:对于所有的异或和区间取最大值。
    • 状态计算:
    • rmax[i] = max(rmax[i - 1], query_max(ls[i], lson)).
    • 后面的函数表示从字典树中查找出与ls[i]异或最大的值。因为是前缀和数组,所以求出最大值,也就求出了区间。
    • 像这样子的数组一共又4个。而字典树分为左右两边,
      整体代码如下:
    #include 
    using namespace std;
    
    const int N = 2e5 + 10, INF = 0x3f3f3f3f;
    
    int n;
    int a[N], ls[N], rs[N]; //数组与前缀异或和 后缀异或和
    int lmax[N], lmin[N], rmax[N], rmin[N]; //左右区间的最值
    int lson[1 << 22][2], rson[1 << 22][2], idx; //左边区间的字典树以及右边区间的字典树。
    
    void Insert(int x, int son[][2])
    {
        int r = 0;
        for (int i = 20; i >= 0; i--)
        {
            int u = x >> i & 1;
            if (!son[r][u])
                son[r][u] = ++idx;
    
            r = son[r][u];
        }
    }
    
    
    int query_max(int x, int son[][2])
    {
        int r = 0, res = 0;
        for (int i = 20; i >= 0; i--)
        {
            int u = x >> i & 1;
            if (son[r][!u])
            {
                res += 1 << i;
                r = son[r][!u];
            }
            else    
                r = son[r][u];
        }
    
        return res;
    }
    
    int query_min(int x, int son[][2])
    {
        int r = 0, res = 0;
        for (int i = 20; i >= 0; i--)
        {
            int u = x >> i & 1;
            if (!son[r][u])
            {
                res += 1 << i;
                r = son[r][!u];
            }
            else 
                r = son[r][u];
        }
    
        return res;
    }
    
    
    int main()
    {
        //读入数据
        scanf("%d",&n);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        
        //初始化四个数组
        for (int i = 0; i <= n + 1; i++)
        {
            rmax[i] = lmax[i] = 0;
            rmin[i] = lmin[i] = INF;        
        }
    
        Insert(0, lson);
        for (int i = 1; i <= n; i++)
        {
            ls[i] = ls[i - 1] ^ a[i];
            lmax[i] = max(lmax[i - 1], query_max(ls[i], lson));
            lmin[i] = min(lmin[i - 1], query_min(ls[i], lson));
            Insert(ls[i], lson);
        }
    
        idx = 0;
        Insert(0, rson);
        for (int i = n; i >= 1; i--)
        {
            rs[i] = rs[i + 1] ^ a[i];
            rmax[i] = max(rmax[i + 1], query_max(rs[i], rson));
            rmin[i] = min(rmin[i + 1], query_min(rs[i], rson));
            Insert(rs[i], rson);
        }
        int res = 0;
        for (int i = 1; i <= n - 1; i++)
            res = max(res, max(lmax[i] - rmin[i + 1], rmax[i + 1] - lmin[i]));
        
        printf("%d\n", res);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    10. 子树的大小

    在这里插入图片描述
    题目要求:
    对于每一行的数据表示n个节点,完全m叉树,求节点k的子树有多少个,包括k节点在内。
    思路:
    其实只要找出k节点的左右孩子lc,rc就好了,感觉这个需要不断的尝试,才能找到规律。
    lc = (k - 1) * m + 2, rc = k * m + 1.

    • 有了这两个公式,便能求出对于任意一个节点的左右孩子,又因为他是完全m叉树,
    • 所以只有其左孩子和右孩子都小于等于节点n的话,就能计算出该层的节点个数是多少。
    • 当发现左孩子大于n的时候,退出循环即可。
    • 当发现右孩子大于等于n的时候,是rc = n, 进行最后一次操作就好了。
    #include 
    using namespace std;
    
    typedef long long LL;
    
    int T;
    int n,m,k;
    
    int Helper()
    {
    	int res = 1;
    	LL lc = k, rc = k;
    	if (k > n)	//节点不存在
    		return 0;
    		
    	bool flag = true;
    	while (flag)
    	{
            //计算左右孩子
    		lc = (lc - 1) * m + 2;
    		rc = rc * m + 1;
    		//左孩子大于总结点个数就说明该层啥也没有,不需要增加了,直接break就好了。
    		if (lc > n)
    			break;
            //右孩子大于等于n的话,将右孩子变成n 执行最后一次节点增加操作即可。
    		if (rc >= n)
    		{
    			rc = n;
    			flag = false;
    		}
    		
    		res += rc - lc + 1;
    	} 
    	
    	return res;
    }
    
    
    int main()
    {
    	scanf("%d",&T);
    
    	while (T --)
    	{
    		scanf("%d%d%d",&n,&m,&k);
    		printf("%d\n",Helper());
    	}
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
  • 相关阅读:
    机器人机械臂抓取综述
    WebSocket 入门案例
    git奇怪报错
    智慧公厕:城市公共厕所的未来之路
    Redis占用内存过高怎么办
    如何让 GPT-4 帮你写出优质Prompt
    HarmonyOS ArkUI实战开发-窗口模块(Window)
    Windows系统SVN SERVER迁移。从服务器A迁移到服务器B
    C++高级教程学习---文件和流
    无病呻吟之高三回忆随笔@_@
  • 原文地址:https://blog.csdn.net/2201_75313100/article/details/137075910