牛客寒假算法基础集训营1

菜鸡一个,只做出两道,先挂一下,剩下的会了继续更

1、小a的计算器
题目描述 :
小a的数学基础实在太差了,以至于他只会用计算器算数。他的计算器比较特殊,只有
,+ ,-, *,/ (即加减乘除)四种运算。
经过一番周折,小a终于算出了他想要的数,但是他却忘记了最初的数是什么。不过幸运的是他记下了整个操作序列,他想请你帮他算出最初的数

输入描述:
第一行两个整数n, X,分别表示操作次数和最终的数接下来n行表示操作序列,每行两个数opt, x
若opt= 1则表示将当前数加x
若opt= 2,则表示将当前数减x
若opt= 3,则表示将当前数乘x
若opt=4,则表示将当前数除以x

输出描述:
一个整数表示最初的数

示例1
输入
4 6
1 3
2 1
3 3
4 2
输出
2

示例2
输入
3 292
3 2
4 3
4 3
输出
1314

备注:
n⩽100,0<X⩽10 ^18

数据保证:

  1. 最初的数在进行操作时不会超过long long范围
  2. 如果你的程序合法,那么运算中所有的数均为整数,所有的除法均为整除!
  3. 不会出现整数被0除的情况

只需要用一下STL里面的stack就可以了,签到题,没什么难度

#include<iostream>
#include<stack>
using namespace std;
struct p{
	int choice;
	int x;
};
stack <struct p> s;
int main()
{
	long long int ans;
	int n;
	cin>>n>>ans;
	while(n--)
	{
		struct p temp;
		cin>>temp.choice>>temp.x; 
		s.push(temp);
	}
	while(!s.empty())
	{
		struct p temp=s.top();
		s.pop();
		switch(temp.choice)
		{
			case 1:ans-=temp.x;break;
			case 2:ans+=temp.x;break;
			case 3:ans/=temp.x;break;
			case 4:ans*=temp.x;break;
		}
	}
	cout<<ans<<endl;
	return 0;
}

2、小a与204
题目描述
小a非常喜欢204这个数字,因为′a′+′k′=204现在他有一个长度为n的序列,其中只含有2,0,4这三种数字设ai为序列中第i个数,你需要重新排列这个数列,使得∑(ai−ai−1)^2最大(公式的含义是:每个数与前一个数差的平方的和)
注意:我们默认a0=0

输入描述:
第一行一个整数n接下来一行n个整数,第i个数表示ai

输出描述:
输出一个整数,表示∑(ai−ai−1)^2的最大值

示例1
输入
2
2 4
输出
20

示例2
输入
3
2 0 4
输出
36

示例3
输入
5
2 4 0 2 4
输出
52

备注:
1⩽n⩽105
保证ai为2/0/4中的数

用了一个比较笨的办法,先排序,再根据一小一大交替,这样能保证和最大,猜测有简便方法,因为只有2、0、4这三个数这个条件没用上,可能是根据这三个数的个数有简便方法

#include<iostream>
#include<algorithm>
using namespace std;
int num[100005];
int main()
{
	int n;
	cin>>n;
	num[0]=0;
	for(int i=1;i<=n;i++)
		cin>>num[i];
	sort(num,num+n+1);
	int max=n;
	int min=0;
	int cnt=0;
	int ans=0;
	while(1)
	{
		if(max==min)break;
		ans+=(num[max]-num[min])*(num[max]-num[min]);
		if(cnt%2==0) min++;
		else max--;
		cnt++;
	}
	cout<<ans<<endl;
	return 0;
}

3、小a的轰炸游戏

题目描述
小a正在玩一款即时战略游戏,现在他要用航空母舰对敌方阵地进行轰炸
地方阵地可以看做是n×m的矩形航空母舰总共会派出q架飞机。
飞机有两种,第一种飞机会轰炸以(xi,yi)为中心,对角线长为li的正菱形(也就是两条对角线分别于x轴 y轴平行的正方形),而第二种飞机只会轰炸正菱形的上半部分(包括第xi行)
(具体看样例解释)
现在小a想知道所有格子被轰炸次数的异或和
注意:不保证被轰炸的格子一定在矩形范围内,若越界请忽略

输入描述:
第一行三个整数n,m,q,分别表示矩阵的长/宽/询问次数
接下来q行,每行四个整数opt,x,y,l,表示飞机类型,轰炸的坐标,以及对角线长度
保证l为奇数!

输出描述:
一个整数,表示所有格子被轰炸次数的异或和

示例1
输入:
4 5 4
1 2 2 1
1 3 3 5
1 3 2 3
2 2 4 3

输出:
2

参考答案用的是二维差分,原谅我技术不够从来没听说过这种方法,用的笨办法,超时,过了一半,5000+人一个AC的都没有,果然难,实在想不出还有什么可以继续优化的地方了,先挂一个过了一半超时的代码

#include<stdio.h>
#include<memory.h>
int Map[1010][1010];
int n,m;
int main()
{
	int q;
	scanf("%d %d %d",&n,&m,&q);
	memset(Map,0,sizeof(Map));
	while(q--)
	{
		int opt,x,y,l;
		scanf("%d %d %d %d",&opt,&x,&y,&l);
		x--;y--;
		if(opt==1)
		{
			int tx=x;
			if(tx<0)tx=0;
			for(int i=l;i>=1;i-=2)
			{
				int j=y-i/2;
				if(j<0)j=0; 
				for(;j<=y+i/2&&j<m;j++)
				{
					Map[tx][j]++;
				}
				tx--;
				if(tx<0)break;
			}
			if(tx>=n)break;
			tx=x+1;
			for(int i=l-2;i>=1;i-=2)
			{
				int j=y-i/2;
				if(j<0)j=0; 
				for(;j<=y+i/2&&j<m;j++)
				{
					Map[tx][j]++;
				}
				tx++;
				if(tx>=n)break;
			}
		}
		else
		{
			int tx=x;
			if(tx<0)tx=0;
			for(int i=l;i>=1;i-=2)
			{
				int j=y-i/2;
				if(j<0)j=0; 
				for(;j<=y+i/2&&j<m;j++)
				{
					Map[tx][j]++;
				}
				tx--;
				if(tx<0)break;
			}
			if(tx>=n)break;
		}
	}
	int ans=0;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			ans=ans^Map[i][j];
		 } 
	}
	printf("%d\n",ans);
	return 0;
}

难点在于怎么确定被轰炸的范围,给的是轰炸的中心坐标和轰炸半径,也就是说要根据这个来确定一个菱形,最简单的情况是轰炸的菱形完全在地图内,这种情况下就通过坐标和半径来确定就可以,每一行差2个点,这样就可以确定,更复杂一点的情况是轰炸区域比地图还要大,这样就需要进行缩小,从轰炸中心的一行往上不断缩小就可以了,菱形的下半部分是对称的,代码不需要有太大改动,用笨办法做只能做到这样,暂时还想不出来哪里还可以优化了,答案的二维差分如下,反正我是看不懂了

#include<bits/stdc++.h>
#define LL long long 
using namespace std;
const int MAXN = 1e6 + 10, MAX = 5001, INF = 1e9 + 10, base = 1201;
void chmin(int &a, int b) {a = (a < b ? a : b);}
void chmax(int &a, int b) {a = (a > b ? a : b);}
int sqr(int x) {return x * x;}
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int N, M, Q, type[MAXN], a[MAX][MAX], opt[MAX][MAX][2], b[MAX][MAX], xx[MAXN], yy[MAXN], ll[MAXN]; 
void solve1(int x, int y, int len) {
    len /= 2;
    a[x - len][y]++;
    if(len == 0) return ;
    opt[x - len + 1][y - 1][0]++; 
    opt[x - len + 1][y + 2][1]--;
    opt[x + 1][y - len - 1][0]--;
    opt[x + 1][y + len + 2][1]++;
}
void solve2(int x, int y, int len) {
    len /= 2;
    if(len == 0) return ;
    a[x + len][y]++;
    opt[x + len - 1][y - 1][0]++;
    opt[x + len - 1][y + 2][1]--;
    opt[x][y - len][0]--;
    opt[x][y + len + 1][1]++;
}
void print() {
    puts("");
    for(int i = 0; i <= N + base * 2; i++, puts(""))
        for(int j = 0; j <= M + base * 2; j++)
            printf("%d ", a[i][j] + b[i][j]);
}
signed main() {
    N = read(); M = read(); Q = read();//采用更加快速的读入方式 
    for(int i = 1; i <= Q; i++) 
		type[i] = read(), xx[i] = read() + base, yy[i] = read() + base, ll[i] = read();//读入方式坐标半径 
    for(int i = 1; i <= Q; i++) 
		solve1(xx[i], yy[i], ll[i]);
    for(int i = 0; i <= N + 2 * base; i++) {
        int sum = 0;
        for(int j = 0; j <= M + 2 * base; j++) {
            sum += opt[i][j][0] + opt[i][j][1];
            a[i][j] += sum;
            opt[i + 1][j - 1][0] += opt[i][j][0];
            opt[i + 1][j + 1][1] += opt[i][j][1];
        }	
    }
    memcpy(b, a, sizeof(a));
    memset(a, 0, sizeof(a));
    memset(opt, 0, sizeof(opt));
    for(int i = 1; i <= Q; i++) 
		if(type[i] == 1) solve2(xx[i], yy[i], ll[i]);
    
	for(int i = N + base * 2; i >= 0; i--) {
        int sum = 0;
        for(int j = 0; j <= M + 2 * base; j++) {
            sum += opt[i][j][0] + opt[i][j][1];
            a[i][j] += sum;
            opt[i - 1][j - 1][0] += opt[i][j][0];
            opt[i - 1][j + 1][1] += opt[i][j][1];
        }
    }
    //print();
    int ans = 0;
    for(int i = 1 + base; i <= N + base; i++)
        for(int j = 1 + base; j <= M + base; j++)
            ans ^= (a[i][j] + b[i][j]);
    cout << ans;
    return 0;
}

4、小a的排列

题目描述
小a有一个长度为n的排列。定义一段区间是"萌"的,当且仅当把区间中各个数排序后相邻元素的差为1现在他想知道包含数x,y的长度最小的"萌"区间的左右端点
也就是说,我们需要找到长度最小的区间[l,r],满足区间[l,r]是"萌"的,且同时包含数x和数y如果有多个合法的区间,输出左端点最靠左的方案。

输入描述:
第一行三个整数N,x,y,分别表示序列长度,询问的两个数
第二行有n个整数表示序列内的元素,保证输入为一个排列

输出描述:
输出两个整数,表示长度最小"萌"区间的左右端点

输入
5 2 3
5 2 1 3 4
输出
2 4

输入
8 3 5
6 7 1 8 5 2 4 3
输出
5 8

备注:
保证2⩽n⩽105,1⩽x,y⩽n

考点在于模拟和分析,意思就是说找一个连续序列包含给出的两个数,很像蓝桥杯2013年本科B里面的一道题,但是这一道更难,大体思路就是先找到给你的L和R,之后扩展空间,直到符合连续空间为止,这里补充一点,一般的连续空间问题都要用到公式:max-min=r-l,即区间最大值-区间最小值等于右端点-左端点。具体看官方的代码,自己写的能过25%的数据,因为没有考虑到扩展区间的更优方法。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10, INF = 1e9 + 10;
void chmin(int &a, int b) {a = (a < b ? a : b);}
void chmax(int &a, int b) {a = (a > b ? a : b);}
int N, L, R, a[MAXN], pos[MAXN];
int main() {
    cin>>N>>L>>R;
    for(int i = 1; i <= N; i++) 
	cin>>a[i] , pos[a[i]] = i;//数字a[i]的位置在pos[a[i]]的位置 
    L = pos[L]; R = pos[R];//把L、R换成对应所在的位置 
    if(L > R) swap(L, R);
    int mx = -1, mn = INF, l = L, r = R;
    for(int i = L; i <= R; i++) chmin(mn, a[i]), chmax(mx, a[i]);//确定区间最大值和最小值 
    for(int i = mn; i <= mx; i++) chmin(l, pos[i]), chmax(r, pos[i]);//在最大值和最小值所在区间之间选择左右两端点,保证区间是连续的 
    while(l < L || R < r){
        int tmn = mn, tmx = mx;
        while(l < L) chmin(tmn, a[--L]), chmax(tmx, a[L]);//在扩大的区间里找新的最大和最小值 
        while(R < r) chmin(tmn, a[++R]), chmax(tmx, a[R]);
        for(int i = tmn; i <= mn; i++) 
			chmin(l, pos[i]), chmax(r, pos[i]); //在扩大的区间里找新的左右端点 
        for(int i = mx; i <= tmx; i++) 
			chmin(l, pos[i]), chmax(r, pos[i]);
        mn = tmn; mx = tmx;
    }
    cout << l << ' ' << r;
    return 0;
}

强烈吐槽官方给的测试用例,情况太少,这里补充一组测试用例
输入:
9 7 4
1 3 6 9 7 5 8 4 2
以这组测试用例为例,需要的L为7,R为4,交换一下后为L=4,R=7,就是说目前区间为{7,5,8,4}这四个数,要想成为连续空间,需要补一个6,在对应的POS数组中查询4-7的位置,从而求出要想使其 变成连续空间,就要把原区间扩展到L=6,R=7,这时候从L到R扩展为{6,9,7,5,8,4},由于扩展了一部分,要重新找区间最大值和区间最小值,再从对应POS数组里找位置,发现这时不需要再扩展空间,此时就说明现在的L-R为要求的萌区间,输出L和R的位置即可。
主要的难点在于怎么扩展区间,连续区间的特点要求要找最小和最大,再找最小和最大对应的位置,在这个新区间中再找最大最小,直到不需要再扩展区间。

猜你喜欢

转载自blog.csdn.net/weixin_43849505/article/details/86600085