[XUPT_ACM]寒假第一次比赛题解

写在前面:本次比赛共11题,设计的是A-E是简单题,F-H为中等题,I-K为难题,考虑到比赛的友好性,我们并没有将题目难度打乱,而是基本上按照从简单到难来排序。比赛的总体出题和预期的差不多,其中B题出题数较少而实际上B题是比较简单的一道题(可能是题目没看懂吧),而J题的过题数比预计的要多很多(看来大家数学都不错)。

比赛地址:https://vjudge.net/contest/280657

下面就对比赛的题目做一下简单的解答(题目方法不唯一,给出的解法仅供参考):

神秘连接

A题     B题     C题     D题     E题     F题     G题     H题     I题     J题     K题

 

A题


题意:

魔法球可以用魔法晶体来合成:

黄=2*黄(晶体)

绿=1*黄(晶体)+1*蓝(晶体)

蓝=3*蓝(晶体)

已知有A个黄色晶体B个蓝色晶体,要得到x黄y绿z蓝个魔法球,问最少还差几个晶体。


分析:

算出x黄y绿z蓝个魔法球所需的黄色晶体AS和蓝色晶体总数BS,如果不够就在结果中加上(AS-A)和(BS-B)。

注意要用long long!


参考代码:

#include <cstdio>
#include <iostream>
// 注意要用long long
typedef long long ll;
using namespace std;

ll a,b,as=0,bs=0;
ll x,y,z,ans=0;

int main()
{
	cin >> a >> b;
	cin >> x >> y >> z;
	
    // 计算所需的黄色晶体和蓝色晶体数量
	as=2*x+y;
	bs=3*z+y;
    // 如果不够就更新答案
	if (as>a) ans+=(as-a);
	if (bs>b) ans+=(bs-b);
	
	cout << ans << endl;
	return 0;
}

 

B题


题意:

这个题意可能比较难理解,其实就是有从左到右n个塔,然后相邻两个塔之间a-b层是相通的。问从 tafa层 到 tbfb层 的最少步数,移动一次算一步。


分析:

这里我分了以下几种情况:

1.在同一个塔里的:abs(fa-fb)

2.不在一个塔里的:

(fa<a):先走到a层,再走到tb塔,再走到fb层:abs(fa-a)+abs(ta-tb)+abs(fb-a)

(fa>b):先走到b层,再走到tb塔,再走到fb层:abs(fa-b)+abs(ta-tb)+abs(fb-b)

    其他   :直接走到tb塔,再走到fb层:abs(ta-tb)+abs(fa-fb)


参考代码:

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

int n,h,a,b,k,ans;
int ta,fa,tb,fb;

int main()
{
	cin >> n >> h >> a >> b >> k;
	while(k--)
	{
		ans=0;
		cin >> ta >> fa >> tb >> fb;
		if (ta==tb)
            // 直接走到fb层
			ans=abs(fa-fb);
		else
			if (fa<a)
                // 先走到a层,再走到tb塔,再走到fb层
				ans=abs(fa-a)+abs(ta-tb)+abs(fb-a);
			else if (fa>b)
                // 先走到b层,再走到tb塔,再走到fb层
				ans=abs(fa-b)+abs(ta-tb)+abs(fb-b);
			else
                // 直接走到tb塔,再走到fb层
				ans=abs(ta-tb)+abs(fa-fb);
		cout << ans << endl;
	}
	return 0;
} 

 

C题


题意:

键盘被偷了,键盘编号是x,x+1....x+n-1。已知还剩下的键盘编号,问最少丢失的键盘数目。


分析:

直接令剩下键盘编号中最小的为x最大的为x+n-1,于是总键盘数就为最大-最小+1,丢失的键盘数目就是 最大-最小+1-n 。


参考代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

int n;
int a[1005];

int main()
{
	cin >> n;
	for (int i=1;i<=n;i++)
		cin >> a[i];
    // 排序
	sort(a+1,a+n+1);
	cout << a[n]-a[1]+1-n << endl;
	return 0;
}

 

D题


题意:

玩游戏拿数字,先手要使最后剩下数字最小,后手相反,拿到只剩下一个为止。


分析:

先手肯定拿最大,后手肯定拿最小。当然纯暴力就可以了,不过有一个稍微优雅一点的解法。我们先排序,然后排完序之后的最中间那个数字就是剩下的数字(偶数的话是中间偏左),直接输出这个数字就是最终结果。


参考代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

int n;
int a[1005];

int main()
{
	cin >> n;
	for (int i=1;i<=n;i++)
		cin >> a[i];
    // 排序
	sort(a+1,a+n+1);
    // a[(n+1)/2]就是中间的数
	cout << a[(n+1)/2] << endl;
	return 0;
}

 

E题


题意:

用1*2的地砖铺地板,只能横着铺,可以碎成两个1*1。中间有一个矩形不用铺,给出位置。


分析:由于只能横着铺,就不用考虑怎么铺了,只要考虑需要几个1*1的即可。

我们把拖拉机(中间矩形)的上下和左右分开来考虑。

对于拖拉机上下:每一行需要的1*1就是m%2,而上有(x1-1)行,下有(n-x2)行,总共有(x1-1+n-x2)行

对于左右:每一行需要的1*1,左边需要(y1-1)%2,右边需要(m-y2)%2,而总共有(x2-x1+1)行

综上所述:sum(1*1的个数)=(x1-1+n-x2)*(m%2)+(x2-x1+1)*((y1-1)%2+(m-y2)%2)

最终的答案:(sum+1)/2;


参考代码:

#include <cstdio>
#include <iostream>
using namespace std;

int n,m,x1,x2,y1,y2;
int sum=0;

int main()
{
	cin >> n >> m >> x1 >> y1 >> x2 >> y2;
    // 1*1的总数
	sum=(x1-1+n-x2)*(m%2)+(x2-x1+1)*((y1-1)%2+(m-y2)%2);
	cout << (sum+1)/2 << endl;
	return 0;
}

 

F题


题意:

你有一种特殊的笔,你需要伪造签名,问你能不能伪造?


分析:

暴力枚举每个位置,用vis来标记。每次遇到'#'我们都有两种处理方法。

之前没标记过的(第一次遇到):这个点必为特殊笔的左上角,涂色,如果发现涂色的地方有一处不是'#',则不可能伪造。

之前标记过的(不是第一次遇到):可以这个点为左上角涂色(也可以不涂),如果可以涂则涂色(这是贪心的思想)。

枚举完还可以伪造则说明可以 伪造。


参考代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

int n,m;
char Map[1005][1005];
int vis[1005][1005];
// 以某个位置为左上角涂色
int dir[9][2]={ {0,0},{0,1},{0,2},{1,0},{1,2},{2,0},{2,1},{2,2} };

int main()
{
	memset(vis,0,sizeof(vis));
    // 读入
	cin >> n >> m; getchar();
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=m;j++)
			scanf("%c",&Map[i][j]);
		getchar();
	}
    // 遍历找'#'
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)
		{
            // 为'#'但未标记
			if (Map[i][j]=='#' && !vis[i][j])
			{
                // 越界,不可能涂色,不能伪造
				if (i>n-2 || j>m-2)
				{
					printf("NO");
					return 0;
				}
                // 以(i,j)为左上角涂色,若不可以则说明不能伪造
				for (int k=0;k<9;k++)
				{
					int nx=i+dir[k][0],ny=j+dir[k][1];
					vis[nx][ny]=1;
					if (Map[nx][ny]!='#')
					{
						printf("NO");
						return 0;
					}
				}
			}
            // 为'#'已标记
			else if (Map[i][j]=='#')
			{
                // 判断能否涂色
				int flag=1;
				for (int k=0;k<9;k++)
				{
					int nx=i+dir[k][0],ny=j+dir[k][1];
					if (Map[nx][ny]!='#')
					{
						flag=0;
						break;
					}
				}
                // 若能涂色就涂色
				if (flag)
					for (int k=0;k<9;k++)
					{
						int nx=i+dir[k][0],ny=j+dir[k][1];
						vis[nx][ny]=1;
					}	
			}
		}
    // 遍历结束,可以涂色
	printf("YES\n");
	return 0;
}

 

G题


题意:

问你扫雷怎么样才能赢。


分析:

其实就是看这个棋盘符不符合规则。我们可以碰到一个雷就把周围一圈的数字都减一,如果最后整个棋盘只有雷和0(包括.),就说明符合规则。


参考代码:

#include <cstdio>
#include <iostream>
using namespace std;

int n,m;
// 周围一圈
int dir[8][2]={ {-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1} };
char Map[105][105];
bool flag=true;

// 判断是否在棋盘内
bool judge(int x,int y)
{
	if (x>=0 && x<n && y>=0 && y<m)
		return true;
	else 
		return false;
}

int main()
{
    // 读入
	scanf("%d%d",&n,&m); getchar();
	for (int i=0;i<n;i++)
		scanf("%s",Map[i]);
	for (int i=0;i<n;i++)
		for (int j=0;j<m;j++)
			if (Map[i][j]=='*')
			{
                // 把周围一圈都减一
				for (int k=0;k<8;k++)
				{
					int nx=i+dir[k][0],ny=j+dir[k][1];
                    // 周围一圈不是雷
					if (judge(nx,ny) && Map[nx][ny]!='*')
					{    
                        // 如果减完小于0了,那就肯定不能赢了,直接令flag=false
						if (Map[nx][ny]!='0' && Map[nx][ny]!='.')
							Map[nx][ny]--;
						else
							flag=false;
					}
				}
			}
    // 判断能不能赢
	for (int i=0;i<n;i++)
		for (int j=0;j<m;j++)
            // 必须只有雷和0(包括.)
			if (Map[i][j]!='*' && Map[i][j]!='0' && Map[i][j]!='.')
			{
				flag=false;
				break;
			}

	if (flag)
		cout << "YES" << endl;
	else
		cout << "NO" << endl;
	return 0;
}

 

H题


题意:

形如[:|||:]的就是手风琴字符串,删除字符串s中字符,使其变成手风琴,如果可以输出长度。


分析:

判断能不能变成手风琴就是要确定'[' , ':' , ':' , ']'的位置p1,p2,p3,p4。

其中p4>p3>p2>p1

'[' 直接找最左边的,']' 直接找最右边的确定位置。

':' 就找 '[' 右边第一个和 ']' 左边第一个

然后再找p2-p3之间的 '|'


参考代码:

#include <cstdio>
#include <iostream>
#include <string>
using namespace std;

string s;
int p1=-1,p2=-1,p3=-1,p4=-1,ans=4;

int main()
{
	cin >> s;
    // 找最左边的'['位置
	for (int i=0;i<s.length();i++)
	{
		if (s[i]=='[')
		{
			p1=i;
			break;
		}
	}
    // 没找到,输出-1
	if (p1==-1)
	{
		cout << -1 << endl;
		return 0;
	}
    // 找'['后第一个':'
	for (int i=p1+1;i<s.length();i++)
	{
		if (s[i]==':')
		{
			p2=i;
			break;
		}
	}
    // 没找到,输出-1
	if (p2==-1)
	{
		cout << -1 << endl;
		return 0;
	}
    // 找最右边的']'
	for (int i=s.length()-1;i>=0;i--)
	{
		if (s[i]==']')
		{
			p4=i;
			break;
		}
	}
    // 没找到或者在左侧':'左边,输出-1
	if (p4==-1 || p4<=p2)
	{
		cout << -1 << endl;
		return 0;
	}
    // 找右侧':'
	for (int i=p4-1;i>=0;i--)
	{
		if (s[i]==':')
		{
			p3=i;
			break;
		}
	}
    // 没找到或者在左侧':'左边,输出-1
	if (p3==-1 || p3<=p2)
	{
		cout << -1 << endl;
		return 0;
	}
    // 找两个':'中间的'|'
	for (int i=p2+1;i<=p3-1;i++)
		if (s[i]=='|')
			ans++;
	cout << ans << endl;
	return 0;
}

 

I题


题意:

这个题目就是给你n个范围[Li,Ri],然后把这些范围分成两堆(不能为空),每堆和另外一堆都不能有重合,最后输出每一个范围放在哪一堆(没有输出-1)。


分析:

我们先将这些范围按照L从小到大排序,然后依次放到堆1,堆2,先放1再放2,再放1,不断重复。其中如果放1或者2不可以的话就放回原来那堆。例如前一个范围放的是堆1,现在的范围本应该放堆2,但是放堆2会和堆1冲突,于是我们将其放回堆1(放了肯定不会产生冲突)。依次类推直到放完所有的,如果最后有一个堆为空就说明不存在解,输出-1。


参考代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
 
struct node
{
	int l,r;
	int id;
}a[100005];
 
//lmax控制堆1的最大值,rmax控制堆2的最大值
int ans[100005],lmax,rmax;
 
//按照L升序,L相同R小的在前
int cmp(node a1,node a2)
{
	if (a1.l==a2.l)
		return a1.r<a2.r;
	else
		return a1.l<a2.l;
}
 
//flag1标记堆1不为空,flag2标记堆2不为空
int T,n,flag1,flag2;
 
int main()
{
	cin >> T;
	while(T--)
	{
		lmax=rmax=0; flag1=flag2=0;
		scanf("%d",&n); a[0].id=2;
		//记录id
		for (int i=1;i<=n;i++)
		{
			scanf("%d%d",&a[i].l,&a[i].r);
			a[i].id=i;
		}
		sort(a+1,a+n+1,cmp);
		for (int i=1;i<=n;i++)
		{
			//放堆1
			if (i==1 || ans[a[i-1].id]==2)
			{
				//放堆1不冲突
				if (a[i].l>rmax)
				{
					ans[a[i].id]=1;
					//重要!!!一开始只写了a[i].r,而没有考虑到a[i].r可能小于a[i-1].r
					lmax=max(lmax,a[i].r);
					flag1=1;
				}
				//冲突,放堆2
				else
				{
					ans[a[i].id]=2;
					rmax=max(rmax,a[i].r);
					flag2=1;
				}
			}
			//放堆2
			else
			{
				//放堆2不冲突
				if (a[i].l>lmax)
				{
					ans[a[i].id]=2;
					rmax=max(rmax,a[i].r);
					flag2=1;
				}
				//冲突,放堆1
				else
				{
					ans[a[i].id]=1;
					lmax=max(lmax,a[i].r);
					flag1=1;
				}
			}
		}
		//两堆都有
		if (flag1 && flag2)
		{
			for (int i=1;i<=n;i++)
				printf("%d ",ans[i]);
			printf("\n");
		}
		//只有1堆
		else
			printf("-1\n");
	}
	return 0;
}

 

J题


题意:

数学题,题目很简单,问敏敏学姐能不能逃走。


分析:

这题是队友出的,我只是个数论渣渣啊。

老师肯定是在圆上绕圈圈的,然后老师的角速度就是v2/R。

然后学姐可以在某个Rx上和老师一起保持同样的速度绕圈圈,这个时候学姐的角速度是v1/Rx=v2/R。

于是乎,一起绕圈圈的时候学姐的Rx=v1*R/v2。

然后呢学姐现在如果和老师一起绕圈圈他最近可以距离圈圈的边缘(R-Rx),即和老师保持最远距离(R+Rx),此时老师,中心和学姐是在一条直线上的。

然后现在就是学姐能不能逃走的关键了,如果她能在最短距离(R-Rx)里逃走那就成功了,此时老师要绕半个圆来抓学姐。

于是学姐的时间就是:(R-Rx)/v1

老师绕圈的时间就是:(pi*R)/v2,tips:这个pi有很多方法计算,也可以手写多写几位

比较一下两个时间就知道学姐能不能逃脱了,嘿嘿。


参考代码:

#include <cstdio>
#include <iostream>
#include <cmath>
const double pi=atan(1.0)*4;
using namespace std;

double R,v1,v2,Rx,t1,t2;

int main()
{
	while(cin >> R >> v1 >> v2)
	{
		Rx=v1*R/v2;
		t1=(R-Rx)/v1;
		t2=(pi*R)/v2;
		if (t1<=t2)
			cout << "Yes" << endl;
		else
			cout << "No" << endl;
	}
	return 0;
}

 

K题


题意:

哦这题就是防ak的,其实我也不咋会。


分析:

好像是个贪心来着。


参考代码:

这里是大佬的解答:

https://blog.csdn.net/maxichu/article/details/47867991

posted on 2019-02-02 19:05  Radium_1209  阅读(272)  评论(0编辑  收藏  举报

导航