电子科技大学-经济与管理学院-SME杯-ACM竞赛解题报告(1)

电子科技大学-经济与管理学院-SME杯-ACM竞赛解题报告

摘要


先说一下个人感受吧。
首先所有的题目确实不难,几乎全是模拟题,也就是说不需要任何NOIP的知识就能够解题,只要你能够找到比较好的解题思路,再把它用程序写出来就行。最多最多就考察了一个读入优化和一个单调队列。
然后就是…
既然题目这么简单怎么我的成绩还是那么惨不忍睹啊orz(很久没练了完全不会审题了)
好了下面是真经报告:


题目讲解

A题

  1. 题意分析:
    本题要求我们从一个数列中选择至少一个数出来 ,并且选出的数的和最少为X,极差最大为d,询问最少有多少种选法。
  2. 解题思路:
    很明显这道题用DFS是最快最简单的算法:首先让数组从小到大排列再依次枚举数,DFS时记录这个最开始枚举的数并同时计算和和极差,满足则继续向后枚举,不满足则退回起点枚举下一个数。
    BFS也可以做,但是懒得打代码了(X)
  3. 注意事项:
    注意此题要用long long,不要头铁全用int
  4. AC代码:
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;

int n,ans=0;
long long minv,maxd;
int num[20];
int jud[20];

void getans(int pos,long long sum,long long mnn)//通过子函数嵌套实现DFS,保存当前位置pos,本次枚举总和sum,起始数mnn
{
	if(sum>=minv)ans++;
	
	for(int i=pos+1;i<=n;i++)
	  if(num[i]-mnn<=maxd)
		getans(i,sum+num[i],mnn);
}

int main()
{
	cin >> n >> minv >> maxd;
	for(int i=1;i<=n;i++)cin>>num[i];
	sort(num+1,num+n+1);
	for(int i=1;i<=n;i++)getans(i,num[i],num[i]);
	cout << ans << endl;
}

B题

  1. 题意分析:
    本题要求我们计算从一个坐标移动到另一个坐标所需要的最小步数。
  2. 解题思路:
    很明显直接横坐标之差加上纵坐标之差就行了
    没有人用BFS吧
  3. 注意事项:
    如果要用stl的abs()函数记得引用头文件cmath
  4. AC代码:
#include<iostream> 
#include<cstdio>
#include<cmath>
using namespace std;

int t;
int xx1,yy1,xx2,yy2;

int main()
{
	cin >> t;
	while(t--)
	{
		cin >> xx1 >> yy1 >> xx2 >> yy2;
		cout << abs(xx2-xx1)+abs(yy2-yy1) << endl;
	}
} 

C题

  1. 题意分析:
    本题要求我们计算从一个坐标移动到另一个坐标所需要的Z最简指令及步数。
  2. 解题思路:
    这个题可以算一道有点难度的模拟题了。
    为了简化判断,我选择先把机器人统一朝向北面。(如果不这么干而是直接解对于我来说不仅想着头大,代码实现也头大)然后再统一处理当终点分别在机器人的8个方位上如何走,如何输出答案。最后你会发现对于一个方向最简指令的格式与步数是唯一的。
  3. 注意事项:
    第一个是这里:
    【N for North, S for South, E for East, or O for West】
    注意O才是朝西,不是W也不是0。
    第二个是当终点在机器人左上方的时候最简指令是先向前走,而不是先转三圈。这样最长只需要5个指令而不是六个。
    (在这两处跌倒了无数次orz)
  4. AC代码:
#include<iostream> 
#include<cstdio>
#include<cmath>
using namespace std;

char d;
int xx1,yy1,xx2,yy2;
int x,y;

void getposition()
{
		 if(d=='N'){x=xx2-xx1;y=yy2-yy1;}
	else if(d=='S'){x=xx1-xx2;y=yy1-yy2;}
	else if(d=='O'){x=yy2-yy1;y=xx1-xx2;}	
	else if(d=='E'){x=yy1-yy2;y=xx2-xx1;}		
}

int main()
{
	cin >> xx1 >> yy1 >> d >> xx2 >> yy2;
	getposition();
	if(x>0)
	{
		if(y>0)
		{cout<<3<<endl<<"A "<<y<<endl<<"D"<<endl<<"A "<<x;}
		else if(y<0)
		{cout<<4<<endl<<"D"<<endl<<"A "<<x<<endl<<"D"<<endl<<"A "<<-y;}
		else 
		{cout<<2<<endl<<"D"<<endl<<"A "<<x;}
	}
	else if(x<0)
	{
		if(y>0)
		{cout<<5<<endl<<"A "<<y<<endl<<"D"<<endl<<"D"<<endl<<"D"<<endl<<"A "<<-x;}
		else if(y<0)
		{cout<<5<<endl<<"D"<<endl<<"D"<<endl<<"A "<<-y<<endl<<"D"<<endl<<"A "<<-x;}
		else 
		{cout<<4<<endl<<"D"<<endl<<"D"<<endl<<"D"<<endl<<"A "<<-x;}		
	}
	else
	{
		if(y>0)
		{cout<<1<<endl<<"A "<<y;}
		else if(y<0)
		{cout<<3<<endl<<"D"<<endl<<"D"<<endl<<"A "<<-y;}
		else 
		{cout<<0<<endl;}	
	}
} 

D题

  1. 题意分析:
    本题要求我们判断一个字符串是否是一个环形字符串的子串(或者说是否按字母顺序排列)
  2. 解题思路:
    因为在ASCII码中小写字母的编码是连续的,所以很明显直接判断字符是否连续即可,最多再特判一下za。
  3. 注意事项:
    字符,字符串的输入输出操作可能对初学者而言比较难。
  4. AC代码:
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;

int t,jd=0;
char p[1050];

int main()
{
    cin>>t;
    while(t--)
    {
        cin>>p;
        for(int i=1;i<strlen(p);i++)
          if(p[i]!=p[i-1]+1)
            if(p[i-1]!='z'||p[i]!='a')
              {jd=1;continue;}
    
        (jd)?(cout<<"NO"<<endl):(cout<<"YES"<<endl);
        jd=0;
    }
    return 0;
}

E题

  1. 题意分析:
    本题要求我们对一个数组中的数进行有限次的求反操作,使得数组中数的总和尽可能大。
  2. 解题思路:
    很明显,我们要尽可能让负数越少越好,负数的和越少越好,这样才能使总和尽可能大。所以一开始一定是从大(绝对值大)到小对负数进行求反,能变多少是多少。
    如果变完后还有剩余的操作步数,接着要特判是否有0存在。有零意味着不用对正数进行求反,于是数组的值在负数被转化完后就已经是最大值了。
    如果没有0,那么就从正数中找一个最小数再根据剩余操作步数进行求反,从而保证总和最大。
  3. 注意事项:
    在选正数最小值时不要忘了前面的负数,这里的求最小正数要考虑之前的负数。(在这里死了好几次orz)
  4. AC代码:
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;

int t,n,k,a,ans;
int neg[10050];
int numpos,numneg,numzro;

int main()
{
	cin >> t;
	while(t--)
	{
		cin >> n >> k;
		
		int jud=999;
		numpos=numneg=numzro=0;	ans=0;
		
		for(int i=1;i<=n;i++)
		{
			cin >> a;	ans+=a;//	cout << a << " " << ans << endl;
			if(a>0){jud=min(a,jud);numpos++;}
			if(a<0)neg[++numneg]=-a;//cout<<neg[numneg]<<" ";
			if(a==0)numzro++;
		}
	
		if(numneg)sort(neg+1,neg+numneg+1);
	
//		cout<<endl;
//		for(int i=1;i<=numneg;i++)cout<<neg[i]<<" ";
//		cout<<endl;
		
		while(k--)
		{
			if(numneg)
			{
				ans+=2*neg[numneg];
				jud=min(neg[numneg],jud);
				numneg--;
				continue;
			}
			if(numzro)break;
			ans-=2*jud;jud=-jud;
		}
		cout << ans << endl;
	}
	
}

F题

  1. 题意分析:
    两个人分别在两个区间内往返运动,本题要求我们算这两人会相遇几次,
  2. 解题思路:
    模拟就可以做,用变量对应的数的变化模拟人的移动,用几个变量记录方向,但要注意细节。
  3. 注意事项:
    一开始若两人在同一点视为相遇了一次。
  4. AC代码:
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;

int t,k;
int l1,r1,p1,d1;
int l2,r2,p2,d2;

void simulate()
{
	cin >> k;	
	
	int ans=0;    
	if(p1==p2)ans++;
	
	for(int i=1;i<=k;i++)
	{
		if(p1==l1)d1=1;	if(p1==r1)d1=0;
		if(p2==l2)d2=1; if(p2==r2)d2=0;
		if(d1&&p1!=r1)p1+=1;	else if(!d1&&p1!=l1)p1-=1;
		if(d2&&p2!=r2)p2+=1;	else if(!d2&&p2!=l2)p2-=1;			
		if(p1==p2)ans++;

	}
	
	cout<<ans<<endl;
}

int main()
{
	cin >> t;
	while(t--)
	{
		cin >> l1 >> r1 >> p1 >> d1;
		cin >> l2 >> r2 >> p2 >> d2;
		simulate();
	}
}


G题

  1. 题意分析:
    本题询问我们能否通过将一个数减一,其余数加一的操作,将一个数列中的所有数变为一个值
  2. 解题思路:
    注意这道题中数列中数的值并不重要
    注意到每次操作该数与其他数相差2
    考虑奇偶性。。。
    然后你会发现这道题判断所有数是否同奇偶就完了
  3. 注意事项:
    主要是从题目中发现考点是奇偶性这点不一定想得到
    哦还有这道题竟然还卡cin,将cin解绑或进行读入优化就行。
  4. AC代码:
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
#include<string>
using namespace std;

int n,t,h;

int main()
{
	scanf("%d",&t);
	while(t--)
	{
		int odd=0,eve=0;
		
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&h);
			if(h%2)	odd++;
			else	eve++;
		}
		
		if(odd&&eve) cout << "no" << endl;
		else 		 cout << "yes" << endl;
	}
	return 0;
}

I题

  1. 题意分析:
    本题询问我们一个奇数项字符串能不能通过翻转变为另一个奇数项字符串,如果有,最少需要多少步
  2. 解题思路:
    字符串处理题,暴力判断就行。我是用变量side判断此时a字符串剩余项的正反,再判断其是否与b字符串相符,步数是否加一。
  3. 注意事项:
    这道题是这次比赛几乎最坑的一道题。以至于我做出来后别人叫我看他代码哪里错了我都答不出来orz 好像只通过判断相邻字符来判断答案是否加一是会炸的,具体原因我有空再想想。。。
  4. AC代码:
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
#include<string>
using namespace std;

int n,t;
int ans=0,jud=0,side=1;
char a[100500],b[100500];

int judge(int i)
{

	if     ((a[i]==b[n-1-i]&&a[n-1-i]==b[i])&&(a[i]==b[i]&&a[n-1-i]==b[n-1-i]))return 0;
	else if((a[i]==b[i]&&a[n-1-i]==b[n-1-i]))return 1;
	else if((a[i]==b[n-1-i]&&a[n-1-i]==b[i]))return 2;                                           
	else return -1;
}

int main()
{
	cin >> t;
	while(t--)
	{
		cin>>a>>b;	n=strlen(a);  ans=jud=0; side=1;//cout<<n<<endl;
		if(n==1)
		{
			if(a[0]==b[0])cout<<0<<endl;
			else cout<<"-1"<<endl;
			continue;
		}
		if(judge(0)==2){ans+=1;side=2;}
		for(int i=1;i<(n-1)/2;i++)
		{
			if((judge(i))==-1){jud=1;break;}
			else if(side!=judge(i))
				if(judge(i)!=0){ans++;}
			if(judge(i)==1)side=1;	
			if(judge(i)==2)side=2;					
			//cout<<i<<endl;
		}
		if(!jud&&(judge(0)!=-1)&&(a[(n-1)/2]==b[(n-1)/2]))cout<<ans<<endl;
		else   cout<<"-1"<<endl;
	//	for(int i=0;i<(n-1)/2;i++)cout<<judge(i);
	}

	return 0;
}


J题

  1. 题意分析:
    本题要求我们判断一个数列中某个数是否是某个区间内的最小数(实际上那个区间的中项就是它)
  2. 解题思路:
    本题终于与NOIP沾了点边。。。考了单调队列的维护。
    首先建一个单调队列,然后就不断存入数,剔除超出区间的数,输出第一项,完了
    然而尴尬的是单调队列当时我学的并不怎么好。。。当时我一般用的STL的单调队列。
    这次我也用了。
    然后我就炸了。
    这里写图片描述
    最后找的网上一个单调队列的轮子才过的。。。
  3. 注意事项:
    单调队列最好自己写,除非你试过很多遍很可靠(像Dijkstra小根堆优化)否则。。。

不要信任STL!!!

  1. AC代码:
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
#include<string>
using namespace std;

int n,t,x,y;
struct node
{
    int rain,date;
};

const int maxn = 100050;
int num[maxn];

typedef pair<int, int> P; 
struct priority_queue {
    P q[maxn];
    int head, tail;
    void clear() { head = tail = 0;}
    void pop(int pos) {
        while (head < tail && pos > q[head].second) ++head;
    }
    void push(int val, int pos) {
        while (head < tail && val < q[tail - 1].first) --tail;
        q[tail++] = make_pair(val, pos);
    }
    bool empty() { return head == tail;}
	node top() { 
		node f;
		f.rain = q[head].first;
		f.date = q[head].second;
		return f;
	}
}que;

int main()
{
//	ios::sync_with_stdio(0); 
//	cin.tie(0);	cout.tie(0);

	cin >> n >> x >> y;
	for(int i=1;i<=n;i++)cin >> num[i];
	
	for(int i=1;i<=min(y,n);i++)que.push(num[i],i);
	
	for(int i=1;i<=n;i++)
	{
		node jud;
		que.pop(i-x);jud=que.top();			
		if(i+y<=n)que.push(num[i+y],i+y);jud=que.top();
		if(jud.date==i){cout<<i<<endl;break;}
	}
	
	return 0;
}

K题

  1. 题意分析:
    本题要求我们判断一个二维字符串能否在经过若干列平移后使得字符串中的‘#’联通,如果有求出一种可能的答案。
  2. 解题思路:
    首先可以判断如果出现‘##’那么一定能够联通
    接着可以判断出没有‘##’又同时有‘#.’,‘.#’那么就不会联通
    最后得出除了上面那种情况其他的情况都是联通的。
    输出就让‘##’在中间‘…’在最后就行
  3. 注意事项:
    字符串的读入,输出与统计可能对初学者有些难度
  4. AC代码:
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cmath>
#include<string>
#include<cstring>
using namespace std;

int s1,s2,s3,s4;
int jd[1050];
char s[1050];

int main() {
	cin >> s;
	for(int i=0;i<strlen(s);i++)
		(s[i]=='#')?(jd[i+1]=1):(jd[i+1]=0);
	cin >> s;
	for(int i=0;i<strlen(s);i++)
	{
		if(s[i]=='#')
		{
			if(jd[i+1])s2++;
			else	 s3++;
		}
		else
		{
			if(jd[i+1])s1++;
			else	 s4++;
		}
	}	
	if(s1&&s3&&!s2)cout << "NO" << endl;
	else 
	{
		cout << "YES" << endl;
		for(int i=1;i<=s1+s2;i++)cout<<"#";	
		for(int i=1;i<=s3+s4;i++)cout<<".";
		cout << endl;
		for(int i=1;i<=s1;i++)cout<<".";		
		for(int i=1;i<=s2+s3;i++)cout<<"#";	
		for(int i=1;i<=s4;i++)cout<<".";	
	}
	
	return 0;
}

// s1   s2   s3   s4
// #    #    .    .
// .    #    #    .

L题

  1. 题意分析:
    经典的过桥问题。
  2. 解题思路:
    注意过桥问题实际上有两种局部4人解
    第一种是由第一个人一直带着后面的人过桥,是最简单但也是接触过此题的同学最容易忘的一种。
    第二种就是经典的(1,2)(1)(3,4)(2)(1,2)的过法了,做过这类题的同学容易想到,一开始不容易想到。
    然后就很简单了,运用分治的思想,对于三个人以上的情况都拆成2+2n+m(m<2)的形式,对于每四个人(最快的两个人和还没过桥的最慢的两个人)判断哪个方案用时最小,然后把用时短的加入ans就完了。
  3. 注意事项:
    不容易想到有两种局部解。
  4. AC代码:
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;

int n;
long long ans;
long long t[120000];

int main() {
	cin >> n;	
	for(int i=1;i<=n;i++)cin>>t[i];
	
	sort(t+1,t+n+1);
	
	while(n>3)
	{
		if(2*t[2]>=t[1]+t[n-1])ans+=2*t[1]+t[n-1]+t[n];
		if(2*t[2] <t[1]+t[n-1])ans+=2*t[2]+t[1]+t[n];
		n-=2;		
	}
	if(n==1)ans+=t[1];
	else if(n==2)ans+=t[2];
	else if(n==3)ans+=t[1]+t[2]+t[3];
	
	cout << ans;
	
	return 0;
}

赛后总结


  1. 审题 很,重,要!(数据范围,是否要解绑cin或使用读入优化)
  2. 题目数据 很,不,靠,谱! 一定要 手,造,样,例!
  3. 不,要,过,分,信,任,STL !
  4. 多,练,大,模,拟!
  5. 多,记,好,轮,子!
  6. Practice Makes Perfect !
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值