a981 求和問題

解題想法

這題我是參考Naukri的code,將他的dfs函式解讀,用自己的話寫而已。

接下來的解法是我自己解讀的,我並不知道他是不是這樣想的。

以範例測資為例:
一開始先做到

5 10 15 20 next=50

加入30

5 10 15 20 30 next=80

這時候再加40就>100,然後40的下一個是50,符合第19行的條件,直接return。
加入40

5 10 15 20 40 next=90

這時會遇到和加入30一樣的狀況,一樣return。
加入50

5 10 15 20 50 next=100

因為=100,所以輸出5 10 15 20 50。
5 10 15 20都做完了,所以i++,20換30。

5 10 15 30 next=60

在5 10 15 30的情況中,搭配40會剛好=100,所以輸出5 10 15 30 40。

以此類推,比較深的都窮舉完後就會慢慢的遞回來,最後做到不能再做為止就全部輸出完了。

AC (0.4s, 316KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
#include<algorithm>
using namespace std;

int n,m,i,l[35],rec[35];
char noAns=1;

void dfs(int crt,int start,int depth){
int i,j,next;
for(i=start;i<n;i++){
next=crt+l[i];
rec[depth]=l[i]; //rec用來記錄next是由哪些數字相加
if(next==m){
for(j=0;j<depth;j++){cout<<rec[j]<<" ";}
cout<<l[i]<<endl;
noAns=0;
}
if(next>m){
if(l[i]<l[i+1]){return;} //連加上l[i]都大於m了,更不用說比l[i]還大的數,直接返回比較快。
}
if(next+l[i]<=m){
dfs(next,i+1,depth+1);
}
}
}

int main(){
cin>>n>>m;
for(i=0;i<n;i++){cin>>l[i];}
sort(l,l+n);
dfs(0,0,0);
if(noAns){cout<<"-1\n";}
return 0;
}

a229 括號匹配問題

解題想法

感覺跟窮舉有關的題目都能用DFS來解,這題和a981 求和問題比較單純些,只須注意左括弧的數量是否大於等於右括弧的數量即可。

心得

這題我有用C和Cpp實作,也藉由這題讓我感受到ZeroJudge好像對C特別友善,畢竟之前寫UVA的題目,它是對Cpp使用者比較友善。
以下是我用C和Cpp測試的結果:
C

AC (0.2s, 72KB)

Cpp

TLE (1s)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

char s[30],first=1;
int n;

void dfs(int crt,int l,int r){
if(crt==2*n){
printf("%s\n",s);
return;
}
if(l<n){
s[crt]='(';
dfs(crt+1,l+1,r);
}
if(r<l){
s[crt]=')';
dfs(crt+1,l,r+1);
}
}

int main(){
while(scanf("%d",&n)==1){
memset(s,0,sizeof(s));
if(first==1){first=0;}
else{printf("\n");}
dfs(0,0,0);
}
return 0;
}

a524. 手機之謎

這題是窮舉法,也是DFS裡很經典的題型。這題看解題報告好像有些人是用prev_permutation()這個函式,我待會也來試試看,不過我DFS的概念還不是很熟悉,所以還是想練一下。這次參考了Naukri的code

AC (0.1s, 316KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream>
using namespace std;

//used用01表示哪些數字用過、哪些沒有
int n, used[9]={0}, pw[8]={0};

void dfs(int len,int num){
int i;

if(len==n){
for(i=0;i<len;i++){cout<<pw[i];}
cout<<endl;
}

for(i=n;i>0;i--){
if(!used[i]){
used[i]=1;pw[len]=i;
dfs(len+1, i-1);
used[i]=0;pw[len]=0;
}
}
}

int main(){
while(cin>>n){
dfs(0,n);
}
return 0;
}

查了一下prev_permutation()怎麼用的時候,發現還有一個next_permutation(),兩者差在prev會將數字從字典序最大開始越排越小,而next會將數字從字典序最小開始越排越大。要使用這兩個函式需要先用algorithm這個函式庫。

AC (0.1s, 324KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
#include<algorithm>
using namespace std;

int main(){
int arr[8], i, n;
while(cin>>n){
for(i=0;i<n;i++){arr[i]=n-i;}
do{
for(i=0;i<n;i++){cout<<arr[i];}
cout<<endl;
}
while(prev_permutation(arr, arr+n));
}
return 0;
}

b701. 我的領土有多大

這一題我參考了高中生解題系統-參考答案 zerojudge code的做法,先對陸地做標記,第一塊標記2、第二塊標記3,以此類推。然後再跑迴圈算出每塊地的東西南北、面積並印出。

AC (2ms, 1.1MB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include<stdio.h>
#define lim 514

int map[lim][lim];

void paint(int m,int y,int x){
map[y][x]=m;

if(map[y][x+1]!=1 && map[y-1][x]!=1 && map[y][x-1]!=1 && map[y+1][x]!=1){return;}

//往右找
if(map[y][x+1]==1){
paint(m,y,x+1);
}

//往下找
if(map[y-1][x]==1){
paint(m,y-1,x);
}

//往左找
if(map[y][x-1]==1){
paint(m,y,x-1);
}

//往上找
if(map[y+1][x]==1){
paint(m,y+1,x);
}
}

int main(){
int x,y,i,j,mark=2;
//歸零
for(i=0;i<lim;i++){
for(j=0;j<lim;j++){
map[i][j]=0;
}
}

//輸入
scanf("%d%d",&x,&y);
for(i=1;i<=y;i++){
for(j=1;j<=x;j++){
scanf("%d",&map[i][j]);
}
}

//標記
for(i=1;i<=y;i++){
for(j=1;j<=x;j++){
if(map[i][j]==1){
paint(mark,i,j);
mark++;
}
}
}

//輸出
int check=2;
while(check<mark){
int area=0,w,n,e,s;

for(i=1;i<=y;i++){
for(j=1;j<=x;j++){
if(map[i][j]==check){
//算面積
area++;

//初始化
if(area==1){
w=j;e=j;n=i;s=i;
}

//最西
if(j<w){w=j;}

//最東
if(j>e){e=j;}

//最南
if(i>s){s=i;}
}
}
}

printf("%d %d %d %d %d\n",w-1,n-1,e-1,s-1,area);
check++;
}

return 0;
}

a280. 小朋友上樓梯

本來以為自己突發奇想的從後往前找會很快,結果…

TLE(1s)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<stdio.h>

int main(){
int n,k,i;
while(scanf("%d%d",&n,&k) != EOF){
int a[k],b[k];

//輸入所有傳送方式
for(i=0;i<k;i++){
scanf("%d%d",&a[i],&b[i]);
}


//先找到終點位置
int loc;
for(i=0;i<k;i++){
if(b[i] == n){
loc = i;
break;
}
}

//從終點往回找
int stair,find;
while(a[loc]!=0){
stair = a[loc];
find = 0;
for(i=0;i<k;i++){
if(b[i] == stair){
loc = i;
find = 1;
break;
}
}
if(!find){break;}
}

if(find){
printf("Ok!\n");
}
else{
printf("Impossib1e!\n");
}
}
return 0;
}

看來用DFS是無法避免的,函式的設計我參考了alan23273850 的刷題筆記,用這個函式跑會將所有點都跑完,最後依據res判斷是否能到達,第19行我也試過=,不過因為所有點都跑完 的特性,如果搜尋的最後一個點不是目的地,那麼dfs(0)就會是false,就算中途有遇到目的地也一樣。所以用|=,這樣只要中途有遇到目的地,true就不會被覆蓋掉。

AC (44ms, 428KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<bits/stdc++.h>
#define speedup ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
using namespace std;

int n;
bool v[101];
vector<int> p[101];

bool dfs(int s){
//成功到達目的地
if(s == n){return true;}

//還沒到目的地就遇到環,一定失敗。
if(v[s]){return false;}

v[s] = true;
bool res = false;
for(int h=0 ; h<p[s].size() ; h++){
res |= dfs(p[s][h]);
}
return res;
}

int main(){
speedup;
int a,b,k,i;
while(cin >> n >> k){
memset(v,false,sizeof(v));
for(i=0;i<101;i++){
p[i].clear();
}
while(k--){
cin >> a >> b;
p[a].push_back(b);
}
if(dfs(0)){
cout << "Ok!\n";
}
else{
cout << "Impossib1e!\n";
}
}
return 0;
}

封面圖源:Pixiv