现场编程大赛 普及组-决赛试题题解
本次比赛思维难度从易到难大约为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;
}