2018年蓝桥杯C/C++B组省赛

题一:第几天

2000年的1月1日,是那一年的第1天。那么,2000年的5月4日,是那一年的第几天?
注意:需要提交的是一个整数,不要填写任何多余内容。

思路:就是考一个平年闰年。=m=

代码:

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

int Month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

bool isLeap(int year){
	return ( year % 4 == 0 && year % 100 != 0 ) || year %400 == 0; 
}

int main(){
	int year, month, day, ans = 0;
	cin >> year >> month >> day;
	Month[2] = isLeap(year) ? 29 : 28;
	for( int i = 1; i < month; ++ i ){
		ans += Month[i];
	}
	ans += day;
	cout << ans;
}

题二:明码

汉字的字形存在于字库中,即便在今天,16点阵的字库也仍然使用广泛。16点阵的字库把每个汉字看成是16x16个像素信息。并把这些信息记录在字节中。一个字节可以存储8位信息,用32个字节就可以存一个汉字的字形了。把每个字节转为2进制表示,1表示墨迹,0表示底色。每行2个字节,一共16行,布局是:

第1字节,第2字节
第3字节,第4字节
....
第31字节, 第32字节

输入:

4 0 4 0 4 0 4 32 -1 -16 4 32 4 32 4 32 4 32 4 32 8 32 8 32 16 34 16 34 32 30 -64 0 
16 64 16 64 34 68 127 126 66 -124 67 4 66 4 66 -124 126 100 66 36 66 4 66 4 66 4 126 4 66 40 0 16 
4 0 4 0 4 0 4 32 -1 -16 4 32 4 32 4 32 4 32 4 32 8 32 8 32 16 34 16 34 32 30 -64 0 
0 -128 64 -128 48 -128 17 8 1 -4 2 8 8 80 16 64 32 64 -32 64 32 -96 32 -96 33 16 34 8 36 14 40 4 
4 0 3 0 1 0 0 4 -1 -2 4 0 4 16 7 -8 4 16 4 16 4 16 8 16 8 16 16 16 32 -96 64 64 
16 64 20 72 62 -4 73 32 5 16 1 0 63 -8 1 0 -1 -2 0 64 0 80 63 -8 8 64 4 64 1 64 0 -128 
0 16 63 -8 1 0 1 0 1 0 1 4 -1 -2 1 0 1 0 1 0 1 0 1 0 1 0 1 0 5 0 2 0 
2 0 2 0 7 -16 8 32 24 64 37 -128 2 -128 12 -128 113 -4 2 8 12 16 18 32 33 -64 1 0 14 0 112 0 
1 0 1 0 1 0 9 32 9 16 17 12 17 4 33 16 65 16 1 32 1 64 0 -128 1 0 2 0 12 0 112 0 
0 0 0 0 7 -16 24 24 48 12 56 12 0 56 0 -32 0 -64 0 -128 0 0 0 0 1 -128 3 -64 1 -128 0 0 

思路:就是按照二进制的规则,把代表每个字节的数进行分解,不过需要注意,用除二取模法得到的二进制序列和真是序列是逆向的。

证明:x=p_i*2^i+p_{i-1}*2^{i-1}+...+p_1*2^1+p_0*2^0(0<=p_i<=1)

我们按照如下步骤进行二进制分解:

(1) 如果当前数是奇数,则得到p_i=1,否则得p_i=0,转步骤(2)。

(2) 将当前的数整除2,若数不为0,则转步骤(1)。

我们尝试分解一次,x=p_i*2^i+p_{i-1}*2^{i-1}+...+p_1*2^1+p_0*2^0(0<=p_i<=1)

(1) 奇偶性决定在p_0,若p_0=0,则当前为偶数,得p_0=0,否则得p_0=1

(2) 此时我们对x做整除2的操作,得到新的值x=p_i*2^{i-1}+p_{i-1}*2^{i-2}+...+p_1*2^0(0<=p_i<=1)。接下来重复可证。

#include<bits/stdc++.h>
using namespace std;
int main(){
	int i, j, k, num;
	for( i = 1; i <= 10; ++ i ){
		for( j = 1 ; j <= 32; ++ j){
			if(j & 1)cout << endl;
			cin >> num;
			string str(8,' ');
			for( k = 7; k >= 0; -- k){
				if(num == 0)break;
				if( num & 1 )str[k] = '.';
				num >>= 1;
			}
			cout << str;
		}	
	}
} 

题三:乘积尾零

如下的10行数据,每行有10个整数,请你求出它们的乘积的末尾有多少个零?

5650 4542 3554 473 946 4114 3871 9073 90 4329 
2758 7949 6113 5659 5245 7432 3051 4434 6704 3594 
9937 1173 6866 3397 4759 7557 3070 2287 1453 9899 
1486 5722 3135 1170 4014 5510 5120 729 2880 9019 
2049 698 4582 4346 4427 646 9742 7340 1230 7683 
5693 7015 6887 7381 4172 4341 2909 2027 7355 5649 
6701 6645 1671 5978 2704 9926 295 3125 3878 6785 
2066 4247 4800 1578 6652 4616 1113 6205 3264 2915 
3966 5291 2904 1285 2193 1428 2265 8730 9436 7074 
689 5510 8243 6114 337 4096 8199 7313 3685 211 

思路:末尾的0会产生,是因为乘数中有2和5,所以我只需要算出里面拥有的因子2和因子5的个数,然后取个数小者即可。

#include<bits/stdc++.h>
using namespace std;
int main(){
	int i, n, num, fac2 = 0, fac5 = 0;
	for(i = 1; i <= 100; ++ i){
		cin >> num;
		while( num % 2 == 0 ){
			num >>= 1;
			++ fac2;
		}
		while( num % 5 == 0 ){
			num /= 5;
			++ fac5;
		}
	} 
	cout << min(fac2,fac5);
}

题四:测试次数

x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。
x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。
如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。
特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。
如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n
为了减少测试次数,从每个厂家抽样3部手机参加测试。
某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?

请填写这个最多测试次数。

注意:需要填写的是一个整数,不要填写任何多余内容。

其实这题是一个非常坑的题目,关键在于该题限制了可以用来摔的手机数量,如果直接二分,就错了,直接二分得到的是在手机数量不限的情况下的最优解。

这题是一个鹰蛋问题模型:https://wenku.baidu.com/view/7d57940ef12d2af90242e6ac.html

我们这么来考虑这个题目:实际上,本题由于存在数量上的限制,我们不能随便摔手机。

A. 当我们只有一部手机的时候,此时很显然,为了保证我们能够摔出结果,因此我们只能从最底层(不一定是第一层,但是是相对的第一层,后面会讲)一层一层地摔,这给了我们一个初始化条件。

B. 当我们有j部手机(j>1)的时候,假设我们当前还有i层没测试,我们可以选择从第k层(相对的第k层)摔下去,那么会有两种结果:

    1) 摔坏了,那么说明手机的耐摔指数一定是小于k的,因此我们从最底层到第k-1层摔,于是我们还需要用j-1部手机来测试剩余的k-1层,对应下述状态为dp[k-1][j-1]

    2)没摔坏,那么说明手机的耐摔指数一定是大于等于k的,因此我们从第k+1层到最高层摔,也就是说,我们需要用j部手机来测试剩余的i-k,对应下述状态为dp[i-k][j]

C. 无论我们有多少部手机,只要在第一层摔坏了,那么只需要这一次就知道耐摔指数为0。

因此读者应该发现了,之所以我们不说最底层是第一层,是因为中间的层完全有可能变成我们下一次摔的最底层。

因此我们知道,该题中我们需要保存的两个信息,一个是待测楼层数,一个是剩余手机数,设dp[i][j]表示当前有i层未测试,有j个手机还可用。在考虑状态转移方程之前,我们先来理解一下题意中所说的最坏的运气下最多需要是什么意思。

在整个摔手机的过程中,我们每次可以选择在第k层摔,但是我们没办法知道手机是摔坏还是没有被摔坏,这是不可控的,因此题目所说的最坏运气也就是要我们在第k层摔坏和不摔坏两种情况下更差的那个;同时,由于k是我们可以选择的,因此最少需要其实对应的是,在这些决策中,情况最好的那个。于是我们可以得到如下的状态转移方程:

                                            dp[i][j] =min_{k=1}^{i} max(dp[k-1][j-1], dp[i-k][j])+1)

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

int dp[1010][5];

int main(){
	int i, j, k;
	for( i = 1; i <= 1000; ++ i ){
		dp[i][1] = i;
	}
	for( j = 2; j <= 3; ++ j){
		for( i = 1; i <= 1000; ++ i){
			dp[i][j] = dp[i][1]; 
			for( k = 1; k <= i; ++ k){
				dp[i][j] = min( dp[i][j], max( dp[k-1][j-1], dp[i-k][j] ) + 1 ) ;
			}
		}
	}
	cout << dp[1000][3];
}

题五:快速排序

#include <stdio.h>

int quick_select(int a[], int l, int r, int k) {
    int p = rand() % (r - l + 1) + l;
    int x = a[p];
    {int t = a[p]; a[p] = a[r]; a[r] = t;}
    int i = l, j = r;
    while(i < j) {
        while(i < j && a[i] < x) i++;
        if(i < j) {
            a[j] = a[i];
            j--;
        }
        while(i < j && a[j] > x) j--;
        if(i < j) {
            a[i] = a[j];
            i++;
        }
    }
    a[i] = x;
    p = i;
    if(i - l + 1 == k) return a[i];
    if(i - l + 1 < k) return quick_select( _____________________________ ); //填空
    else return quick_select(a, l, i - 1, k);
}
    
int main()
{
    int a[] = {1, 4, 2, 8, 5, 7, 23, 58, 16, 27, 55, 13, 26, 24, 12};
    printf("%d\n", quick_select(a, 0, 14, 5));
    return 0;
}


注意:只填写划线部分缺少的代码,不要抄写已经存在的代码或符号。

思路:通过分析代码发现,其实本题的递归函数是要找到数组中第k大的数,该题使用快速排序的paration过程可以做到期望线性复杂度O(n)。代码采用随机生成标准元的方式,通过一次paration过程可以将数组中第i大的数归位。此时我们分析一下:我们要找第k大的元素:

A. 如果k<i,说明k在数组左边,因此我们需要在A[L]~A[i-1]中找到第k大的数。

B. 如果k>i,说明k在数组右边,因此我们需要在A[i+1]~A[R]中找到第k-(i-L+1)大的数。

B. 如果k=i,说明i位置的元素就是我们要找的数,直接返回即可。

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

int quick_select(int a[], int l, int r, int k) {
    int p = rand() % (r - l + 1) + l; // 找到随机bias 
    int x = a[p];  
    {int t = a[p]; a[p] = a[r]; a[r] = t;} // 把基准元素交换到数组尾部 
    int i = l, j = r;
    while(i < j) {
        while(i < j && a[i] < x) i++;
        if(i < j) {
            a[j] = a[i];
            j--;
        }
        while(i < j && a[j] > x) j--;
        if(i < j) {
            a[i] = a[j];
            i++;
        }
    }
    a[i] = x; // i:每次归位的元素 位置为i 
    p = i; 
    // k:找到当前a[l]~a[r]内第k大的值
    if(i - l + 1 == k) return a[i];
    // 要找的元素在右边 
    if(i - l + 1 < k) return quick_select(a, i+1, r, k - ( i - l + 1)); //填空
    else return quick_select(a, l, i - 1, k);
}
    
int main()
{
    int a[] = {1, 4, 2, 8, 5, 7, 23, 58, 16, 27, 55, 13, 26, 24, 12};
    printf("%d\n", quick_select(a, 0, 14, 5));
    return 0;
}

题六:递增三元组

给定三个整数数组
A = [A1, A2, ... AN], 
B = [B1, B2, ... BN], 
C = [C1, C2, ... CN],


请你统计有多少个三元组(i, j, k) 满足:
1. 1 <= i, j, k <= N  
2. Ai < Bj < Ck  


【输入格式】 
第一行包含一个整数N。
第二行包含N个整数A1, A2, ... AN。
第三行包含N个整数B1, B2, ... BN。
第四行包含N个整数C1, C2, ... CN。
对于30%的数据,1 <= N <= 100  
对于60%的数据,1 <= N <= 1000 
对于100%的数据,1 <= N <= 100000 0 <= Ai, Bi, Ci <= 100000 


【输出格式】
一个整数表示答案


【样例输入】
3
1 1 1
2 2 2
3 3 3


【样例输出】
27

思路:其实这道题,本瓜皮在考场上看错题意了,本题的意思,分别在三个部分中找到一个数,使得他们递增,对他们的下标并没有任何约束!!!注意审题啊亲们。于是这道题就很简单了,只要数组有序,我们可以直接二分查找。实际上A数组都不需要有序,但A数组的有序可以加速我们的二分,即如果有A_{i}<B_{j}<C_{k},则必有A_{i+1}<B_{j'}<C_{k'},j'>=j,k'>=k。但是考录到实际的编码,方便起见,我们不考虑那么复杂:

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

const int MaxN = 100010;
int Arr[3][MaxN];

int main(){
	int N, i, j, *Pb, *Pc, ans = 0;
	cin >> N;
	for(i = 1; i <= 3; ++ i){
		for(j = 1; j <= N; ++ j){
			cin >> Arr[i][j]; 
		}
	}
	sort(Arr[1] + 1,Arr[1] + N + 1);
	sort(Arr[2] + 1,Arr[2] + N + 1);
	sort(Arr[3] + 1,Arr[3] + N + 1);
	for(i = 1; i <= N; ++ i){
		Pb = upper_bound(Arr[2]+1,Arr[2]+N+1,Arr[1][i]);
		if( Pb == Arr[2] + N + 1 )continue;
		Pc = upper_bound(Arr[3]+1,Arr[3]+N+1,*Pb);
		if( Pc == Arr[3] + N + 1 )continue;
		ans += (Arr[2] + N + 1 - Pb) * (Arr[3] + N + 1 - Pc); 
	}
	cout << ans;
}

题七:螺旋折线

如图p1.png所示的螺旋折线经过平面上所有整点恰好一次。

对于整点(X, Y),我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。

例如dis(0, 1)=3, dis(-2, -1)=9

给出整点坐标(X, Y),你能计算出dis(X, Y)吗?

å¨è¿éæå¥å¾çæè¿°

思路:观察该图,我们可以发现,从原点出发,按照左上右下的顺序进行扩展,同时步长每扩展两次就增加1,我们可以得到最终点落在哪条线上,从而得到答案。但是这个算法的复杂度为O(n),应该过不了。

#include<bits/stdc++.h>
using namespace std;
int main(){
	int x, y, ans = 0;
	cin >> x >> y;
	int step = 1, sx = 0, sy = 0;
	while( true ){
		// 左
		// 查看目标点是否在这条线上 
		if( sy == y && sx - x <= step ){ // 
			ans += sx - x;
			break;
		}
		sx -= step;
		ans += step;
		// 上 
		// 查看目标点是否在这条线上  
		if( sx == x && y - sy <= step ){ // 
			ans += y - sy;
			break;
		}
		sy += step;
		ans += step;
		++ step;
		// 右
		// 查看目标点是否在这条线上  
		if( sy == y && x - sx <= step ){
			ans += x - sx;
			break;
		}
		sx += step;
		ans += step;
		// 下 
		// 查看目标点是否在这条线上 
		if( sx == x && sy - y <= step ){
			ans += sy - y;
			break;
		}
		sy -= step;
		ans += step;
		++ step;	
	}
	cout << ans ;
	return 0;
}

优化:其实我们的目的是知道最终这个点落在哪个线段上,而线段是由端点决定的,因此我们只需要找到端点的变化规律,即可解题。很明显,左上角端点的变化规则是(-1,1)->(-2,2)->...->(-n,n);右上角端点的变化规则是(-1,1)->(2,2)->...->(n,n);左下角端点的变化规则是(-2,-1)->(-3,-2)->...->(-(n+1),-n);右下角端点的变化规则是(1,-1)->(2,-2)->...->(n,-n),也就是说,我们可以用直线将区域分割,利用x和y之间的关系可以得到点的具体位置处于哪一条线段上。另外,由于螺线的延展规则是左->上->右->下,并且每隔两步增长一次,因此我们可以通过端点差值得到步长。

实际上,我们只需要知道目标点落在哪一条线段上,并且知道是四个方向中的哪一个方向,就可以直接计算了。例如,当目标点落在向左或者向右的线段上时,假设它距离该线段起点距离为dis,且当前这个条线段的步长为step,则最终结果是

                                                                                 dis+2*\sum_{i=1}^{step}i

而如果目标点落在向上或者向下的线段上时,假设它距离该线段起点距离为dis,且当前这个条线段的步长为step,则最终结果是

                                                                        dis+step+2*\sum_{i=1}^{step-1}i

我们来看一个实际例子,以上图为例,假设我们要计算(2,1)到起点的距离:它落在(2,2)到(2,-2)的线段上,并且距离起点(2,2)的距离为1,且方向为从上到下,步长为2-(-2)=4,因此套用公式2,得到距离为1+4+2*(1+2+3)=17。

现在我们再来讨论如何确定它处于哪一条线段上,首先我们可以用线段将区域分割:

于是实际上,可以知道,如果y坐标>0,且处于y=-x和y=x之间,即满足 y>0 和 -y<x<y,则为从左到右的线段,其他依次类推。实际上,我们对于每一种线段都可以得到它的坐标特征:

1. 从左到右: y > 0  ;  -y <= x <= y

2.从右到左: y <= 0 ; y - 1 <= x <= -y

3.从下到上: x < 0  ;x + 1 <= y <= -x

4.从上到下: x > 0 ;-x <= y <= x 

#include<bits/stdc++.h>
using namespace std;
int main(){
	int x, y, ans = 0, LX, RX, UY, DY, step;
	cin >> x >> y;
	if( x == 0 && y == 0 ) cout << 0;
	if( y <= 0 && y - 1 <= x && x <= -y ){ // 从右到左
		// y 坐标不会变 与端点相同 
		LX = y - 1; 
		RX = -y;
		step = LX - RX;
		// 增量 + (1 + .. + step - 1 )* 2 
		cout << (x - RX) + step*(step - 1);
	}
	if( x < 0 && x + 1 <= y && -x <= y ){ // 从下到上 
		// x 坐标不会变 与端点相同 
		UY = -x;
		DY = x + 1;
		step = UY - DY;
		// 增量 + (1 + .. + step -1 *2 ) + step 
		cout << (y - DY) + step * (step - 1) + step ;
	}
	if( y > 0 && - y <= x && x <= y ){ // 从左到右 
		// y 坐标不会变 与端点相同 
		LX = -y; 
		RX = y;
		step = LX - RX;
		cout << (x - LX) + step*(step - 1);
	}
	if( x > 0 && - x <= y && y <= x ){ // 从上到下 
		// x 坐标不会变 与端点相同 
		UY = x;
		DY = -x;
		step = UY - DY;
		cout << (UY - y) + step * (step - 1) + step ;
	}
}

题八:日志统计

标题:日志统计
小明维护着一个程序员论坛。现在他收集了一份"点赞"日志,日志共有N行。其中每一行的格式是:
ts id  
表示在ts时刻编号id的帖子收到一个"赞"。  
现在小明想统计有哪些帖子曾经是"热帖"。如果一个帖子曾在任意一个长度为D的时间段内收到不少于K个赞,小明就认为这个帖子曾是"热帖"。  
具体来说,如果存在某个时刻T满足该帖在[T, T+D)这段时间内(注意是左闭右开区间)收到不少于K个赞,该帖就曾是"热帖"。  
给定日志,请你帮助小明统计出所有曾是"热帖"的帖子编号。  

【输入格式】
第一行包含三个整数N、D和K。  
以下N行每行一条日志,包含两个整数ts和id。  

对于50%的数据,1 <= K <= N <= 1000  
对于100%的数据,1 <= K <= N <= 100000 0 <= ts <= 100000 0 <= id <= 100000  

【输出格式】
按从小到大的顺序输出热帖id。每个id一行。  

【输入样例】
7 10 2  
0 1  
0 10    
10 10  
10 1  
9 1
100 3  
100 3  

【输出样例】
1  
3  

资源约定:
峰值内存消耗(含虚拟机) < 256M

CPU消耗  < 1000ms

思路:我们按照维护n个动态数组,用来记录n个帖子的受赞情况,然后将每个帖子的受赞时间排序后,采用取尺法解决。

#include<bits/stdc++.h>
using namespace std; 
const int MaxN = 100010; 
vector<int> Postings[MaxN];

int main(){
	int N, D, K, ts, id, i, L, R, counts, size;
	cin >> N >> D >> K;
	for( i = 1; i <= N; ++ i){
		cin >> ts >> id;
		Postings[id].push_back(ts);
	}
	for( i = 1; i <= MaxN ; ++ i){
		size = Postings[i].size();
		if( size < K ) continue; //已有的统计信息都不足  所以直接跳过 
		sort( Postings[i].begin(), Postings[i].end());
		counts = 0;
		R = 0; L = 0; counts = 1; 
		
		while( R <= size ){
			while( R < size && Postings[i][R] - Postings[i][L] < D ){
				counts = max( counts, R - L + 1); 
				++ R;
			}
			if( R >= size ) break;
			while( L <= R && Postings[i][R] - Postings[i][L] >= D ){
				++ L;
			}
		}
		
		if( counts >= K ){
			cout << i <<endl;
		}
	}
}

题九:全球变暖

你有一张某海域NxN像素的照片,"."表示海洋、"#"表示陆地,如下所示:
.......
.##....
.##....
....##.
..####.
...###.
.......

其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。  

由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。  


例如上图中的海域未来会变成如下样子:
.......
.......
.......
.......
....#..
.......
.......

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。  


【输入格式】
第一行包含一个整数N。  (1 <= N <= 1000)  
以下N行N列代表一张海域照片。  

照片保证第1行、第1列、第N行、第N列的像素都是海洋。  

【输出格式】
一个整数表示答案。

【输入样例】

.......
.##....
.##....
....##.
..####.
...###.
.......  

【输出样例】
1  

资源约定:
峰值内存消耗(含虚拟机) < 256M

CPU消耗  < 1000ms

思路:直接按照题意实现一个infect函数,用于标记那些会被淹没的岛屿即可。值得注意的是,如果我们直接在原数组上操作,直接在原数组中将陆地直接变海,看起来可行。但是缺没有考虑到这块变淹没形成的海可能会淹没别的岛屿,但是题意指的是只淹没一次,并且是按照同时淹没的原则,而不允许被淹没的陆地再淹没别的陆地 。因此最好的方式是另外开一个数组用来打标记。

代码:

#include<bits/stdc++.h>
using namespace std;
const int MaxN = 1010;

char Map[MaxN][MaxN]; 
bool isOcean[MaxN][MaxN];

int main(){	
	int n, i, j;
	cin >> n;
	for( i = 1; i <= n; ++ i ){
		cin >> Map[i] + 1;
	}
	for( i = 1; i <= n; ++ i){
		for( j = 1; j <= n; ++ j){
			if( Map[i][j] == '.' ){
				isOcean[i][j] = true;
				if( i > 1 && Map[i-1][j] == '#' ){
					isOcean[i-1][j] = true;
				} 
				if( i < n && Map[i+1][j] == '#' ){
					isOcean[i+1][j] = true;
				} 
				if( j > 1 && Map[i][j-1] == '#' ){
					isOcean[i][j-1] = true;
				} 
				if( j < n && Map[i][j+1] == '#' ){
					isOcean[i][j+1] = true;
				} 
			}
		}
	}
	for( i = 1; i <= n; ++ i){
		for( j = 1; j <= n; ++ j){
			cout << ( isOcean[i][j] ? '.' : '#' );
		}
		cout << endl;
	}
	return 0;
}

题十:乘积最大

给定N个整数A1, A2, ... AN。请你从中选出K个数,使其乘积最大。  
请你求出最大的乘积,由于乘积可能超出整型范围,你只需输出乘积除以1000000009的余数。  

注意,如果X<0, 我们定义X除以1000000009的余数是负(-X)除以1000000009的余数。
即:0-((0-x) % 1000000009)

【输入格式】
第一行包含两个整数N和K。  
以下N行每行一个整数Ai。  

对于40%的数据,1 <= K <= N <= 100  
对于60%的数据,1 <= K <= 1000  
对于100%的数据,1 <= K <= N <= 100000  -100000 <= Ai <= 100000  

【输出格式】
一个整数,表示答案。

【输入样例】
5 3 
-100000   
-10000   
2   
100000  
10000  

【输出样例】
999100009

再例如:
【输入样例】
5 3 
-100000   
-100000   
-2   
-100000  
-100000

【输出样例】
-999999829


资源约定:
峰值内存消耗(含虚拟机) < 256M

CPU消耗  < 1000ms

思路:直接贪心。不过需要处理正负数的情况,首先我们将所有的数按照绝对值排序。我们选择绝对值最大的k个值相乘,同时统计参与乘法的负数个数,转如下步骤:

很明显,(*)和(**)所指明的是同一种。

特别注意:

1.不要乘法过程中取模后再比较大小

2.把所有的负数变为取模意义下的对应正数后再做乘法,如果结果为符号,注意是让结果减去Mod而不是直接加负号!!!不要用负值直接参与运算,因为当正负数参杂着运算时,直接乘是不对的。

3. 如果数组有0的话,当整个数组不考虑0时,只能是负数结果或者正负数加起来不足k个,则输出0

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MaxN = 100010;

LL Arr[MaxN];
LL Mod = 1000000009;

bool cmp(LL a,LL b){
	return abs(a) < abs(b);
}

int main(){
	int i, Point, n, k, NegativeCount = 0, PositiveCount = 0, TotalPositive = 0, TotalNegative = 0,MinNegative = 0, MinPositive = 0, ans = 1, temp1, temp2, TarNegative = 0, TarPositive = 0;
	cin >> n >> k;
	
	for( i = 1; i <= n; ++ i){
		cin >> Arr[i];
		if( Arr[i] > 0 ){
			++ TotalPositive; 
		}if( Arr[i] < 0 ){
			++ TotalNegative;
		}
	}
	
	if( TotalPositive + TotalNegative < k ){ 
		cout << 0; 
		return 0;
	} 
	
	if( k == n ){ // 必须要乘全部的数 负数的话 需要减一个模 正数直接输出 
		for( i = 1; i <= n; ++ i){
			ans = ( ans * ( Arr[i] % Mod + Mod ) % Mod + Mod ) % Mod ;  
		}
		cout << (TotalNegative %2 == 1 ? ans - Mod : ans ) ;
		return 0;
	}
	
	sort(Arr+1, Arr+1+n, cmp);
	
	if( TotalNegative == n && k % 2 == 1 ){ // 全都是负数且只能选择奇数个负数 那只能选择绝对值最小的k个 
		for( i = 1; i <= k; ++ i){
			ans = ( ans * ( ( Arr[i] % Mod + Mod ) % Mod ) % Mod + Mod ) % Mod ;  
		}
		cout << ans - Mod;
		return 0;
	}
	
	for( i = n; i >= 1 && NegativeCount + PositiveCount < k; -- i){ //先从后面找k个数  先不算带0的个数  
		if( Arr[i] < 0 ){
			++ NegativeCount;
			MinNegative = Arr[i];
		}
		if( Arr[i] > 0 ){
			++ PositiveCount;
			MinPositive = Arr[i]; // 最小整数 
		}
		//cout << " i:" << i << " NegativeCount + PositiveCount:" << ( NegativeCount + PositiveCount ) << endl;
	} 
	Point = i; // 从1~i是还没有被选择过的数 
	
	if( NegativeCount & 1 ){ // 奇数个负数  后面一定会有一个负数 
		// 选择1:在前面找一个绝对值最大负数   去掉当前的最小正数 
		//     情况1:前面有负数
		if( TotalNegative - NegativeCount > 0 ){
			for( i = Point; i >= 1; -- i){ 
				if( Arr[i] < 0  ){
					TarNegative = Arr[i];
					break;
				}
			} 
		} else{ // 情况2:前面没有负数 
			TarNegative = 0; // 0 * x = 0 < 正数 * 正数 
		}
		
		// 选择2:在卡面找一个最大正数  去掉当前最小的绝对值负数 
		//     情况1:前面有正数
		if( TotalPositive - PositiveCount > 0 ){
			for( i = Point; i >= 1; -- i){
				if( Arr[i] > 0 ){
					TarPositive = Arr[i];
					break;
				}
			} 
		}
		else{ //情况2:前面没有正数 由于全负的情况已经判定过 此处只需要讨论后面有正数的情况
			TarPositive = 0; 
		}

		if( TarNegative * MinNegative <= TarPositive * MinPositive){ // 去掉最小绝对值负数  选择最大正数 
			for( i = n; i > Point; -- i){
				if( Arr[i] != MinNegative )ans =  ( ans * ( Arr[i] % Mod + Mod )  % Mod + Mod ) % Mod;
			}
			ans =  ( ans * ( TarPositive % Mod + Mod ) % Mod + Mod ) % Mod;
		} else{
			for( i = n; i > Point; -- i){// 去掉最小正数  选择绝对值最大负数 
				if( Arr[i] != MinPositive ) {
					ans =  ( ans * ( Arr[i] % Mod + Mod )  % Mod + Mod ) % Mod;
				}
			}
			ans =  ans * ( ( TarNegative % Mod ) + Mod ) % Mod % Mod;
		} 
	} else{ // 偶数个负数 没必要做处理 
		for( i = n; i > n - k; -- i){
			ans = ( ans * Arr[i] % Mod + Mod ) % Mod;
		}
		
	}
	cout << ans;	
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值