河海大学第二十一届现场编程大赛-普及组 决赛题解


本次比赛思维难度从易到难大约为FCEABD。

Problem A 哲哲学长的象棋

  这是一道搜索题。在DFS(深度优先搜索)和BFS(广度优先搜索)中我们选择采用BFS对地图上的点进行标记并计数即可。

  如果采用DFS会导致在标记搜索过的点时出现一定的困难。由于递归的原因,DFS会优先对之后节点进行拓展,如果之前能有方式来到该点,因为剪枝的因素会导致这点剩余步数小于真实可走的步数,导致部分可走的点的缺失。

#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
int ans;
bool Flag[1010][1010];
int n, m, s;
int x, y;
struct node 
{
    int Stps,x,y;
};
node Q[1000010];
int Hd,Tl;
int Go[8][2] = { {1,2},{2,1},{-1,2},{-2,1},{1,-2},{2,-1},{-1,-2},{-2,-1} };
int main()
{
    memset(Flag, true, sizeof(Flag));
    scanf("%d%d%d", &n, &m, &s);
    scanf("%d%d", &x, &y);
    Flag[x][y] = false;
    ans=1;
    Q[0].Stps=0;
    Q[0].x=x;
    Q[0].y=y;
    Hd=0;Tl=0;

    while(Hd<=Tl)
    {
        if(Q[Hd].Stps==s)break;
        for(int i=0;i<8;++i)
        {
            if(Q[Hd].x+Go[i][0] >= 1&& Q[Hd].x+Go[i][0] <= n
            && Q[Hd].y+Go[i][1] >= 1&& Q[Hd].y+Go[i][1] <= m 
            &&Flag[Q[Hd].x+Go[i][0]][Q[Hd].y+Go[i][1]])
            {
                Flag[Q[Hd].x+Go[i][0]][Q[Hd].y+Go[i][1]]=false;
                Q[++Tl].Stps=Q[Hd].Stps+1;
                Q[Tl].x=Q[Hd].x+Go[i][0];
                Q[Tl].y=Q[Hd].y+Go[i][1];
                ++ans;
            }
        }
        ++Hd;
    }
    printf("%d\n", ans);
    return 0;
}

Problem B 弛弛学长的排列

  思路就是枚举每一个周期的长度T=A+B,首先得到男生和女生中最大的编号并相加记为maxx(maxx之后的点没必要去枚举了,这是T可能达到的最大值)。然后枚举1-maxx中的分割点i。

  [1-i]区间就一组男生和女生的排列,可以这么考虑这个问题,当我们确定分割点之后,也就是知道一组符合题意的A和B之后,这组队伍会出现A个男生B个女生A个男生B个女生这样周期性的排列(周期为T=A+B),需要保证所有的男生出现在 [(k-1)T+1,kT+A] 区间,k=1,2,3…而女生需要出现在 [kT+A+1,(k+1)T] 区间,k=1,2,3…我们可以通过取余的方式获得每个编号在他们各自区间中的位置,对男女生位置分别对T取余数之后,男生取余后位置最大的值BoyMax即为所求区间中分界点A的最小值,女生取余后位置最小的值GirlMax-1即为所求区间中分界点A的最小的值。

  因此,通过计算B=T-A(若为负数则不存在答案)的方式,可以求得题目要求的A与B。

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>

using namespace std;

int n,m;
int a[10010],b[10010];
int maxa,maxb;
int aa,bb;

int num(int x,int y)
{
	if(x>0)return x;
	else return y;
}

int main()
{
    int T;
    scanf("%d",&T);
	while(T--)
	{
	    scanf("%d%d",&n,&m);
		maxa=maxb=-1;
		for(int i=1;i<=n;++i)scanf("%d",a+i),maxa=max(maxa,a[i]);
		for(int i=1;i<=m;++i)scanf("%d",b+i),maxb=max(maxb,b[i]);

		int maxx=maxa+maxb;
		for(int i=1;i<=maxx;++i)
		{
			aa=0; bb=0x7fffffff;
			for(int j=1;j<=n;++j)aa=max(aa,num(a[j]%i,i));
			for(int j=1;j<=m;++j)bb=min(bb,num(b[j]%i,i));
			if(bb<=aa)continue;
			else
			{
				printf("%d %d\n",aa,i-aa);
				goto End;
			}
		}
		printf("NO\n");
		End:
		int orzjiangyou=1;
	}
	return 0;
}

Problem C 舟舟学长的数据

  字符串排序题,可以使用C++自带的sort函数。

  需要注意的是无论是C语言中的Strcmp函数以及C++中string类的比较函数都是基于字典序(从头开始对字符逐一比较)的方式进行比较的,不符合题目中的排序条件,需要自定义比较函数,即先比较字符串的长度,若相等再进行逐个字符比较的方式进行比较并排序。

  为了降低难度,我们将这道题的数据量进行了缩小,包含冒泡、插入、选择在内的时间复杂度在O(n2)的排序方式均可以完成这题。如果对自己有更高要求,可以登录到OJ上对该题的数据加强版进行提交。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

string a[10010];

bool cmp(string xx,string yy)//自定义cmp函数
{
	if(xx.length()==yy.length())return xx<yy;
	else return xx.length()<yy.length();
}
int n;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)cin>>a[i];
	sort(a+1,a+1+n,cmp);//调用cmp函数
	for(int i=1;i<=n;++i)cout<<a[i]<<endl;
	return 0;
}

Problem D 洲洲学长的清扫

  题目大意为:题目中存在n*m个房间,现在需要找到最小的体力值使各个房间联通的代价最小。

  基本思路是贪心算法。可以参照最小生成树算法进行解答。关键在于建图,建好图之后跑kruskal算法或者prim算法都可以。建图的时候从上到下,从左往右扫描矩阵,每个点只需要向下和向右建边就可以了,然后边的两个邻点的最小值作为边的权值。可以自己画图理解一下,很形象。然后跑kruskal或者prim都可以,prim可以用堆优化一下(使用priority_queue),但是这题不优化也是可以过的。

  其中kruskal算法或者prim算法对应两种贪心思路,有兴趣的同学可以在线上/线下进行提问或者自行进行学习。

//kruskal算法
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxx1 = 510;
const int maxx2 = 500000;//数组要开大一点
const int inf = 0x3f3f3f3f;
typedef long long ll;
int n, m;
int a[maxx1][maxx1];
int father[510*510];
int cnt;
int direction[2][2] = { {0,1},{1,0} };//定义方向
struct edge {
 int u, v, w;
	edge(int from, int to, int weight) {
		u = from;
		v = to;
		w = weight;
 	}
	edge(){}
}e[maxx2];
//下面的find_函数和Unity函数是并查集模板
int find_(int x) {
	if (x == father[x])return x;
else
  return father[x] = find_(father[x]);
}
void Unity(int x, int y) {
 	int a = find_(x);
 	int b = find_(y);
 	if (a == b)return;
 	father[a] = b;
}
bool cmp(const edge& a, const edge& b) {//自定义cmp函数
 	return a.w < b.w;
}
//核心代码,对边权排序之后每次取出当前最短的边,加入到生成树当中
ll kru() {
 	int num = 0;
 	ll ans = 0;
 	for (int i = 1; i <= cnt; i++) {
  		if (find_(e[i].u) == find_(e[i].v)) {
   		continue;
  		}
 		Unity(e[i].u, e[i].v);
  		ans += e[i].w;
  		num++;
  		if (num == n*m - 1) {
   			return ans;
  		}
 	}
 return ans;
}
int main() {
 	while (cin >> m >> n) {
  		memset(a, 0, sizeof(a));
  		for (int i = 1; i <= n; i++) {
   		for (int j = 1; j <= m; j++) {
    			cin >> a[i][j];
   		}
  	}
  	for (int i = 1; i <= n*m; i++) {
   		father[i] = i;
  	}
  	cnt = 1;
  	for (int i = 1; i <= n; i++) {//向下和向右建图
   		for (int j = 1; j <= m; j++) {
    			for (int k = 0; k <= 1; k++) {
     				int dx = i + direction[k][0];
     				int dy = j + direction[k][1];
     				if (dx >= 1 && dx <= n && dy >= 1 && dy <= m) {
      					e[cnt].u = (i - 1) * m + j;
      					e[cnt].v = (dx - 1) * m + dy;
      					e[cnt++].w = min(a[i][j], a[dx][dy]);
     				}
    			}
   		}
  	}
  	cnt--;
  	sort(e + 1, e + 1 + cnt, cmp);
  	cout << kru() << endl;
 }
 return 0;
}

还有prim算法

//prim算法
//用到了STL的优先队列priority_queue
//核心思想是贪心
#include <string>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstring>
#include <ctime>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <map>

using namespace std;

typedef pair<int, int> pii;
#define gch getchar
#define mp make_pair

int n, m;
int a[510][510];
struct node
{
	pii c,d;
	int v;
};
bool operator < (node xx, node yy)
{
	return (xx.v > yy.v);
}
priority_queue <node> Q;
node tmp;
bool In[510][510];
int ans = 0;

int main()
{
	memset(In, false, sizeof(In));
	scanf("%d%d", &n, &m);
	for (int i = 1;i <= m;++i)
		for (int j = 1;j <= n;++j)
			scanf("%d", &a[i][j]);
	In[1][1] = true;
	tmp.c = mp(1, 1);
	tmp.d = mp(1, 2);
	tmp.v = min(a[1][1], a[1][2]);
	Q.push(tmp);
	tmp.c = mp(1, 1);
	tmp.d = mp(2, 1);
	tmp.v = min(a[1][1], a[2][1]);
	Q.push(tmp);

	while (!Q.empty())
	{
		tmp = Q.top(); Q.pop();
		if (In[tmp.c.first][tmp.c.second] && In[tmp.d.first][tmp.d.second])continue;
		//printf("%d,%d -> %d,%d : %d\n", tmp.c.first, tmp.c.second, tmp.d.first, tmp.d.second, tmp.v);
		if (!In[tmp.d.first][tmp.d.second])
		{
			In[tmp.d.first][tmp.d.second] = true;
			ans += tmp.v;
			tmp.c = tmp.d;
			if (tmp.d.first > 1 && !In[tmp.d.first - 1][tmp.d.second])
			{
				--tmp.d.first;
				tmp.v = min(a[tmp.c.first][tmp.c.second], a[tmp.d.first][tmp.d.second]);
				Q.push(tmp);
				++tmp.d.first;
			}
			if (tmp.d.first < m && !In[tmp.d.first + 1][tmp.d.second])
			{
				++tmp.d.first;
				tmp.v = min(a[tmp.c.first][tmp.c.second], a[tmp.d.first][tmp.d.second]);
				Q.push(tmp);
				--tmp.d.first;
			}
			if (tmp.d.second > 1 && !In[tmp.d.first][tmp.d.second - 1])
			{
				--tmp.d.second;
				tmp.v = min(a[tmp.c.first][tmp.c.second], a[tmp.d.first][tmp.d.second]);
				Q.push(tmp);
				++tmp.d.second;
			}
			if (tmp.d.second < n && !In[tmp.d.first][tmp.d.second + 1])
			{
				++tmp.d.second;
				tmp.v = min(a[tmp.c.first][tmp.c.second], a[tmp.d.first][tmp.d.second]);
				Q.push(tmp);
				--tmp.d.second;
			}
		}
	}
	printf("%d\n", ans);
	return 0;
}

Problem E 玲玲学姐的UNO

  模拟题,已经对UNO规则进行了简化,并且对出牌顺序进行了规定,题目总体难度不大。同时,题面经过多次重构,基本符合编程习惯进行了模块化描述,只需要按照题目进行顺序书写即可。如下程序提供了较为详细的注释。

#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

struct Card
{
	int Color, Number;		//定义一张牌的颜色和数字
	Card(int Cl = -1, int Nb = -1)
	{
		this->Color = Cl;
		this->Number = Nb;
	}
};

struct Player				//用以记录玩家手上的牌信息
{
	int Counter;            //用于记录当前玩家剩下的总的手牌数量
	int Cards[5][11];       //用于记录当前玩家剩下的各种牌分别的数量,Cards[颜色][序号]为对应种类牌的数量
};
Player Players[110];        //创建用于记录100个玩家牌状态的数组

int n, m;					//n表示输入的总玩家数量;m表示输入的牌堆中牌的数量,之后可用于记录牌堆中剩下的牌数

int Superposition;			//用于记录叠加的+2数量

void GameOver(int TheWinner)//令游戏结束
{
	if (TheWinner == -1)//没有游戏赢家的抽完牌的牌局
	{
		puts("???");
		exit(0);
	}
	printf("%d\n", TheWinner);
	exit(0);
}

Card Get_Card()//抽取一张牌
{
	if (m == 0)					//如果牌已经被抽完了仍需要抽牌,结束游戏
		GameOver(-1);
	int a, b;
	scanf("%d%d", &a, &b);		//读取顶端牌的信息
	m--;
	return Card(a, b);			//返回顶端的牌的信息
}

void Get_A_Card(int ThePlayer)	//Player抽取一张牌
{
	Card Temp = Get_Card();
	Players[ThePlayer].Counter++;
	Players[ThePlayer].Cards[Temp.Color][Temp.Number]++;
	return;
}

void Add_A_Card(int ThePlayer, Card TheCard)//将该牌加入Player的手牌
{
	Players[ThePlayer].Counter++;
	Players[ThePlayer].Cards[TheCard.Color][TheCard.Number]++;
	return;
}

void Play_A_Card(int ThePlayer, Card &LastCard)//返回值为真则已经打出卡牌,为假未打出卡牌
{
	if (LastCard.Number == -1)//若前一张打出的牌为+2牌,但进行过结算
	{
		for (int i = 0; i <= 10; ++i)//枚举同一颜色的
			if (Players[ThePlayer].Cards[LastCard.Color][i])
			{
				Players[ThePlayer].Counter--;
				Players[ThePlayer].Cards[LastCard.Color][i]--;
				if (i == 10)Superposition = 2;//如果打出的是功能牌+2,叠加器准备
				LastCard.Color = LastCard.Color; LastCard.Number = i;	//更新最后一张打出的牌的信息
				if (Players[ThePlayer].Counter == 0) GameOver(ThePlayer);//若该玩家打完了牌,游戏结束
				return;
			}
		for (int i = 1; i <= 4; ++i)//枚举其他颜色+2牌
			if (Players[ThePlayer].Cards[i][10])
			{
				Players[ThePlayer].Counter--;
				Players[ThePlayer].Cards[i][10]--;
				Superposition = 2;
				LastCard.Color = i; LastCard.Number = 10;	//更新最后一张打出的牌的信息
				if (Players[ThePlayer].Counter == 0) GameOver(ThePlayer);//若该玩家打完了牌,游戏结束
				return;
			}
		Card Temp = Get_Card();//抽一张牌
		if (Temp.Color == LastCard.Color || Temp.Number == 10)//判断抽的牌是否可以使用
		{
			LastCard.Color = Temp.Color;
			LastCard.Number = Temp.Number;
			if (LastCard.Number == 10) Superposition = 2;
		}
		else Add_A_Card(ThePlayer, Temp);
		return;
	}
	if (Players[ThePlayer].Cards[LastCard.Color][LastCard.Number])//看有没有完全相同的牌
	{
		Players[ThePlayer].Counter--;
		Players[ThePlayer].Cards[LastCard.Color][LastCard.Number]--;
		if (LastCard.Number == 10)Superposition += 2;//如果是功能牌+2,叠加
		LastCard.Color = LastCard.Color;
		LastCard.Number = LastCard.Number;
		if (Players[ThePlayer].Counter == 0) GameOver(ThePlayer);
		return;
	}
	for (int i = 1; i <= 4; ++i)//枚举其他颜色,数字相同的牌
		if (Players[ThePlayer].Cards[i][LastCard.Number])
		{
			Players[ThePlayer].Counter--;
			Players[ThePlayer].Cards[i][LastCard.Number]--;
			if (LastCard.Number == 10)Superposition += 2;//如果是功能牌+2,叠加
			LastCard.Color = i; 
			LastCard.Number = LastCard.Number;
			if (Players[ThePlayer].Counter == 0) GameOver(ThePlayer);//若该玩家打完了牌,游戏结束
			return;
		}
	if (LastCard.Number == 10)//如果上一张牌是+2,现在这个玩家已经没有可以打出的其他牌了
	{
		while (Superposition--) Get_A_Card(ThePlayer);//抽取+2叠加的牌
		LastCard.Number = -1;//将+2牌置为无害
	}
	for (int i = 0; i <= 10; ++i)//找同一颜色、序号最小的牌
	{
		if (Players[ThePlayer].Cards[LastCard.Color][i])
		{
			Players[ThePlayer].Counter--;
			Players[ThePlayer].Cards[LastCard.Color][i]--;
			if (i == 10)Superposition = 2;//如果打出的是功能牌+2,叠加器准备
			LastCard.Color = LastCard.Color;
			LastCard.Number = i;
			if (Players[ThePlayer].Counter == 0) GameOver(ThePlayer);//若该玩家打完了牌,游戏结束
			return;
		}
	}
	if (LastCard.Number != -1)//如果不是上一张为+2无法打出+2的情况
	{
		Card Temp = Get_Card();//抽一张牌
		if (Temp.Color == LastCard.Color || Temp.Number == LastCard.Number)//判断抽的牌是否可以使用
		{
			LastCard.Color = Temp.Color;
			LastCard.Number = Temp.Number;
			if (LastCard.Number == 10) Superposition = 2;
		}
		else Add_A_Card(ThePlayer, Temp);
		return;
	}
	//剩下的为+2无法打出+2的情况,仍留下1种出牌方式:不同颜色的+2
	for (int i = 1; i <= 4; ++i)
	{
		if (Players[ThePlayer].Cards[i][10])
		{
			Players[ThePlayer].Counter--;
			Players[ThePlayer].Cards[i][10]--;
			Superposition = 2;
			LastCard.Color = i;
			LastCard.Number = 10;
			if (Players[ThePlayer].Counter == 0) GameOver(ThePlayer);//若该玩家打完了牌,游戏结束
			//虽然这里应该是不可能结束游戏的。。
			return;
		}
	}
	return;
}

void Init()//用于初始化玩家状态,同时包含读入数据的步骤
{
	memset(Players, 0, sizeof(Players));//将所有玩家手牌数据置0
	Superposition = 0;//叠加的+2数量置0
	scanf("%d", &n);//读入总的玩家数量
	m = n * 4;//将每个玩家的手牌放入牌堆
	for (int i = 1; i <= n; ++i)//每个玩家抽四次牌
		for (int j = 1; j <= 4; ++j)
			Get_A_Card(i);
	scanf("%d", &m);
}

void Play()
{
	Card Now_Card = Get_Card();		//记录当前上一张牌
	int Now_Player = 1;				//记录当前将要出牌的玩家号
	if (Now_Card.Number == 10)Now_Card.Number = -1;//若果初始牌是+2,将它标记为已经无害的+2
	while (true)
	{
		Play_A_Card(Now_Player, Now_Card);
		Now_Player++;
		if (Now_Player > n)Now_Player = 1;
	}
}

int main()
{
	Init();
	Play();
	return 0;
	//虽然,永远不会遇到这个return 0。但请相信,总有这么一个与这个return 0相似的人等着你
}

Problem F 源源学姐的新歌

  耐心看完题目、理解题意,不要进行额外的思考即可完成该题。题意为构造数列,使用1~100的数字构造每串数列的开头和结尾均为1的连续数列。
  看懂题目后可以用简单的方式解决。如连续输出1和2,就可以符合题目要求。

#include <cstdio>
#include <cstdlib>
using namespace std;
int T;
int n;

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n/2;++i)printf("1 2 ");
        printf("1\n");
    }
    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值