河海大學第二十一屆現場編程大賽-普及組 決賽題解


本次比賽思維難度從易到難大約爲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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章