現場編程大賽 普及組-決賽試題題解
本次比賽思維難度從易到難大約爲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;
}