1. 程式人生 > >HFOI2017.07.10校內賽(普及組)題解

HFOI2017.07.10校內賽(普及組)題解

1:Maze
描述
    Acm, a treasure-explorer, is exploring again. This time he is in a special maze, in which there are some doors (at most 5 doors, represented by 'A', 'B', 'C', 'D', 'E' respectively). In order to find the treasure, Acm may need to open doors. However, to open a door he needs to find all the door's keys (at least one) in the maze first. For example, if there are 3 keys of Door A, to open the door he should find all the 3 keys first (that's three 'a's which denote the keys of 'A' in the maze). Now make a program to tell Acm whether he can find the treasure or not. Notice that Acm can only go up, down, left and right in the maze.
輸入
    The input consists of multiple test cases. The first line of each test case contains two integers M and N (1 < N, M < 20), which denote the size of the maze. The next M lines give the maze layout, with each line containing N characters. A character is one of the following: 'X' (a block of wall, which the explorer cannot enter), '.' (an empty block), 'S' (the start point of Acm), 'G' (the position of treasure), 'A', 'B', 'C', 'D', 'E' (the doors), 'a', 'b', 'c', 'd', 'e' (the keys of the doors). The input is terminated with two 0's. This test case should not be processed.
輸出
    For each test case, in one line output "YES" if Acm can find the treasure, or "NO" otherwise.
樣例輸入
    4 4
    S.X.
    a.X.
    ..XG
    ....
    3 4
    S.Xa
    .aXB
    b.AG
    0 0
樣例輸出
    YES

    NO

從起始點開始dfs,並在dfs的過程中記錄門的座標位置,並拾取鑰匙。如果收集的鑰匙數量足夠,則從門的位置開始再次進行一次dfs,直到無法繼續dfs為止。(判斷收集的鑰匙是否足夠開啟門的部分在dfs中完成,可以用queue儲存可以進行dfs的起始點

程式碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=22;
const int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
char s[maxn][maxn];
bool door_reach[5];
int door_xy[2][5];
int key_tot[5],key_get[5];
struct node{int x,y;};
queue<node>q;
int m,n,fx,fy,tx,ty;
void dfs(int x,int y)
{
	s[x][y]='X';
	for(int i=0;i<4;i++)
	{
		if(s[x+dx[i]][y+dy[i]]=='.')dfs(x+dx[i],y+dy[i]);
		if(s[x+dx[i]][y+dy[i]]>='a'&&s[x+dx[i]][y+dy[i]]<='e')
		{
			key_get[s[x+dx[i]][y+dy[i]]-'a']++;
			if(key_get[s[x+dx[i]][y+dy[i]]-'a']==key_tot[s[x+dx[i]][y+dy[i]]-'a']
			&&door_reach[s[x+dx[i]][y+dy[i]]-'a'])
			q.push({door_xy[0][s[x+dx[i]][y+dy[i]]-'a'],door_xy[1][s[x+dx[i]][y+dy[i]]-'a']});
			dfs(x+dx[i],y+dy[i]);
		}
		if(s[x+dx[i]][y+dy[i]]>='A'&&s[x+dx[i]][y+dy[i]]<='E')
		{
			door_reach[s[x+dx[i]][y+dy[i]]-'A']=true;
			door_xy[0][s[x+dx[i]][y+dy[i]]-'A']=x+dx[i];
			door_xy[1][s[x+dx[i]][y+dy[i]]-'A']=y+dy[i];
			if(key_get[s[x+dx[i]][y+dy[i]]-'A']==key_tot[s[x+dx[i]][y+dy[i]]-'A'])q.push({x+dx[i],y+dy[i]});
		}
	}
}
int main()
{
	while(scanf("%d%d",&m,&n)&&(m+n))
	{
		memset(s,'X',sizeof s);
		memset(door_reach,false,sizeof door_reach);
		memset(key_tot,0,sizeof key_tot);
		memset(key_get,0,sizeof key_get);
		memset(door_xy,0,sizeof door_xy);
		while(!q.empty())q.pop();
		for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)
		{
			scanf(" %c",&s[i][j]);
			if(s[i][j]=='S')fx=i,fy=j,s[i][j]='.';
			if(s[i][j]=='G')tx=i,ty=j,s[i][j]='.';
			if(s[i][j]>='a'&&s[i][j]<='e')key_tot[s[i][j]-'a']++;
		}
		q.push({fx,fy});
		while(!q.empty())
		{
			node l=q.front();q.pop();
			dfs(l.x,l.y);
		}
		if(s[tx][ty]=='X')printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}
附:感謝hqq和lyb大神幫我查錯!

2:漢諾塔問題
描述
    約19世紀末,在歐州的商店中出售一種智力玩具,在一塊銅板上有三根杆,最左邊的杆上自上而下、由小到大順序串著由64個圓盤構成的塔。目的是將最左邊杆上的盤全部移到中間的杆上,條件是一次只能移動一個盤,且不允許大盤放在小盤的上面。
    這是一個著名的問題,幾乎所有的教材上都有這個問題。由於條件是一次只能移動一個盤,且不允許大盤放在小盤上面,所以64個盤的移動次數是:18,446,744,073,709,551,615
    這是一個天文數字,若每一微秒可能計算(並不輸出)一次移動,那麼也需要幾乎一百萬年。我們僅能找出問題的解決方法並解決較小N值時的漢諾塔,但很難用計算機解決64層的漢諾塔。
    假定圓盤從小到大編號為1, 2, ...
輸入
    輸入為一個整數後面跟三個單字元字串。
    整數為盤子的數目,後三個字元表示三個杆子的編號。
輸出
    輸出每一步移動盤子的記錄。一次移動一行。
    每次移動的記錄為例如 a->3->b 的形式,即把編號為3的盤子從a杆移至b杆。
樣例輸入
    2 a b c
樣例輸出
    a->1->c
    a->2->b
    c->1->b

題目說的非常清楚,直接遞迴解決。前半部分先將前n-1個盤子移動到第三根柱子上,然後將最大的盤子移動到第二根柱子上,最後一個部分將第三根柱子上的盤子移動到第二根柱子上。

程式碼如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
void hanoi(int n,char a,char b,char c)
{
	if(n>1)hanoi(n-1,a,c,b);
	printf("%c->%d->%c\n",a,n,b);
	if(n>1)hanoi(n-1,c,b,a);
}
int main()
{
	int n;
	char a,b,c;
	scanf("%d",&n);
	scanf(" %c %c %c",&a,&b,&c);
	hanoi(n,a,b,c);
	return 0;
}
3:大盜阿福
描述
    阿福是一名經驗豐富的大盜。趁著月黑風高,阿福打算今晚洗劫一條街上的店鋪。
    這條街上一共有 N 家店鋪,每家店中都有一些現金。阿福事先調查得知,只有當他同時洗劫了兩家相鄰的店鋪時,街上的報警系統才會啟動,然後警察就會蜂擁而至。
    作為一向謹慎作案的大盜,阿福不願意冒著被警察追捕的風險行竊。他想知道,在不驚動警察的情況下,他今晚最多可以得到多少現金?
輸入
    輸入的第一行是一個整數 T (T <= 50) ,表示一共有 T 組資料。
    接下來的每組資料,第一行是一個整數 N (1 <= N <= 100, 000) ,表示一共有 N 家店鋪。第二行是 N 個被空格分開的正整數,表示每一家店鋪中的現金數量。每家店鋪中的現金數量均不超過 1000 。
輸出
    對於每組資料,輸出一行。該行包含一個整數,表示阿福在不驚動警察的情況下可以得到的現金數量。
樣例輸入
    2
    3
    1 8 2
    4
    10 7 6 14
樣例輸出
    8
    24
提示
    對於第一組樣例,阿福選擇第 2 家店鋪行竊,獲得的現金數量為 8 。
    對於第二組樣例,阿福選擇第 1 和 4 家店鋪行竊,獲得的現金數量為 10 + 14 = 24 。
經典的序列型動歸問題。

令a[i]表示編號為i的店鋪的現金數量,f[i]表示阿福從編號為1的店鋪洗劫到編號為i的店鋪所得的最大現金數量。

則阿福可以選擇放棄這一家店鋪或者洗劫這一家店鋪。

因為店鋪中的現金數量是正整數,所以狀態轉移方程為:

f[i]=max{f[i-1],f[i-2]+a[i]};

時間複雜度是O(N),空間複雜度可以降到最低O(1)。

程式碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100010;
int f[maxn]={},a[maxn]={},T,n;
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=0;i<n;i++)scanf("%d",&a[i]);
		f[0]=a[0];f[1]=max(a[0],a[1]);
		for(int i=2;i<n;i++)f[i]=max(f[i-2]+a[i],f[i-1]);
		printf("%d\n",f[n-1]);
	}
	return 0;
}