PKU演習問メモ(9/5)
ノルマ終わらないやばい
No. | 問題名 | 問題の種類および解法 | 難易度 |
---|---|---|---|
1577 | Falling Leaves | グラフ・二分探索木 | ★★★★☆ |
1590 | Palindromes | 実装 | ★★☆☆☆ |
1325 | Machine Schedule | 二部グラフの最小辺被覆 | ★★★☆☆ |
1313 | Booklet Printing | 実装 | ★★☆☆☆ |
1473 | There's Treasure Everywhere! | 実装 | ★★☆☆☆ |
1577 Falling Leaves
問題概要
各辺の値が英大文字一文字(またはnull)であるような二分探索木がある。
この木の葉をむしり、左側に葉から順にその文字を出力する。
つぎにその残りの木に同じ操作をする。これを木がなくなるまで繰り返す。
このような操作により出力された文字列が与えられた時、元の二分探索木を復元し、それを行きがけ順で出力せよ。
条件を満たす木はただ一通りに定まるとしてよい。
解法
そんな難しくないはずなのにだいぶ苦戦した……ので難易度高くしておいたw
まず、問題文で考える木は二分探索木なので、各ノードにつき子の数は最大2つ。
更に、左側の子の値は自分より小さく、右側の子の値は自分より大きい。
根から葉を復元する方針で行く。
ある段階で出力される葉は、その前の段階では葉ではなかったので、
根から葉を復元している際に、葉をつける条件として、
- 上の二分探索木の条件を満たす
- 全ての(さっきの)葉に子をつける
ことが必要になる。これを探索により定めていけばよい。
木のノード数は最高でも26なのでこの方針で十分間に合う。
ソースコード
char in[30][30]; int l[30],r[30],n,m,cld[30],cn; char tree[30]; void pre(int c) { putchar(tree[c]); if(l[c]>=0)pre(l[c]); if(r[c]>=0)pre(r[c]); } void getcld(int c) { if(l[c]<0)cld[cn++]=c; else getcld(l[c]); if(r[c]<0)cld[cn++]=c; else getcld(r[c]); } bool rec(int p,int q,char *s) { if(!s[p]) { for(;q<cn;q++)if(l[cld[q]]<0&&r[cld[q]]<0)return 0; return 1; } int c=cld[q]; if(l[c]<0&&tree[c]>s[p]) { l[c]=n; tree[n++]=s[p]; if(rec(p+1,q,s))return 1; l[c]=-1; n--; } if(r[c]<0&&tree[c]<s[p]) { r[c]=n; tree[n++]=s[p]; if(rec(p+1,q,s))return 1; r[c]=-1; n--; } if((l[c]>=0||r[c]>=0)&&q+1<cn&&rec(p,q+1,s))return 1; return 0; } int main() { do { m=0; n=1; rep(i,30)tree[i]=0,l[i]=r[i]=-1; while(scanf("%s",in[m]),isalpha(in[m][0]))m++; tree[0]=in[m-1][0]; for(int i=m-2;i>=0;i--) { cn=0; getcld(0); rec(0,0,in[i]); } pre(0); puts(""); } while(in[m][0]!='$'); return 0; }
1590 Palindromes
問題概要
与えられた文字列が
- 回文
- 線対称
- 回文かつ線対称
- いずれでもない
のどれかを判定せよ。
回文とは逆から読んでも自身と同じになるような文字列であり、線対称な文は、
それの文字列の中心を通り、鉛直な軸について対称移動しても自身と同じになる文字列をいう。
ある文字を鏡文字にした文字の表は与えられる(省略)
解法
特に工夫は必要なく通る。
mirroredのほうは逆順に、文字を鏡文字にしながら自身と一致するかを見ていけばよい。
ソースコード
char in[25]; int main() { char rev[256]={0}; char *revs="AAE3HHIIJLLJMMOOS2TTUUVVWWXXYYZ5112S3E5Z88"; for(int i=0;revs[i];i++)rev[revs[i]]=revs[++i]; while(~scanf("%s",in)) { bool pali=1,mir=1; for(int i=0,j=strlen(in)-1;i<=j;i++,j--) { if(in[i]!=in[j])pali=0; if(rev[in[i]]==0||rev[in[j]]==0|| rev[in[i]]!=in[j]||rev[in[j]]!=in[i])mir=0; } printf("%s -- ",in); if(pali&&mir)puts("is a mirrored palindrome."); else if(pali)puts("is a regular palindrome."); else if(mir)puts("is a mirrored string."); else puts("is not a palindrome."); puts(""); } return 0; }
1325 Machine Schedule
問題概要
二台の機械A,Bがある。Aはn通り、Bはm通りの動作モードがある。
k個のタスクがあたえられる。それらはAのモードa,Bのモードbでのいずれかにより処理できる。
機械の動作モードを変更するには手間がかかるので、できるだけその回数を最小にしたい。
そのような回数を求めよ。初め、二つの機械のモードはともに0である。
n,m<100, k<1000を満たす。
解法
複数入力ケースがあるので対応させないとWA.
n個の赤い頂点とm個の青い頂点をもった二部グラフを作る。
1つのタスクがモードaとモードbのいずれかで処理できるとき、赤い頂点のa番目と青い頂点のb番目を辺で結ぶ。
すると、機械の動作モードの変更回数はグラフの最小辺被覆となる。
つまり、二部グラフの最大マッチングに帰結する。
(0番のモードは最初に処理できるので、そのタスクは辺を張る必要はない)
ソースコード
int n,m,k; vector<vi> e; int p[1000]; bool v[1000]; bool match(int s) { if(s<0)return 1; fr(i,e[s])if(!v[*i]) { v[*i]=1; if(match(p[*i]))return p[*i]=s,p[s]=*i,1; } return 0; } int main() { while(scanf("%d",&n),n) { scanf("%d%d",&m,&k); e.clear(); e.resize(n+m); rep(i,k) { int dmy,a,b; scanf("%d%d%d",&dmy,&a,&b); if(a==0||b==0)continue; e[a].pb(b+n); e[b+n].pb(a); } int ans=0; rep(i,n+m)p[i]=-1; rep(i,n) { rep(j,n+m)v[j]=0; if(match(i))ans++; } printf("%d\n",ans); } return 0; }
1313 Booklet Printing
問題概要
両面印刷で、折りたたまれた本を印刷するにはたとえば4ページの本なら、
1枚目の表に左側に4ページ目、右側に1ページ目
裏の左側に2ページ目、右側に3ページ目を印刷する。
ページの数が与えられたとき、各紙の裏表の左右に印刷されるページ番号を出力せよ。
解法
backと:の間のスペースを忘れるとPEになる。同じ間違いをしている人が結構いるような……
1枚目の表に(最後のページ) 1ページ目、
裏に2ページ目 最後から2番目のページ……と印刷されてく。
それを配列か何かに書き込んで出力すればよい。
ソースコード
int main() { int n; while(scanf("%d",&n),n) { int m=n/4+(n%4!=0),s[25][4]={0}; rep(i,m) { s[i][0]=4*m-2*i; s[i][1]=2*i+1; s[i][2]=2*i+2; s[i][3]=4*m-2*i-1; } rep(i,m)rep(j,4)if(s[i][j]>n)s[i][j]=0; printf("Printing order for %d pages:\n",n); rep(i,m) { if(s[i][0]||s[i][1]) { printf("Sheet %d, front: ",i+1); if(!s[i][0])printf("Blank, "); else printf("%d, ",s[i][0]); if(!s[i][1])printf("Blank\n"); else printf("%d\n",s[i][1]); } if(s[i][2]||s[i][3]) { printf("Sheet %d, back : ",i+1); if(!s[i][2])printf("Blank, "); else printf("%d, ",s[i][2]); if(!s[i][3])printf("Blank\n"); else printf("%d\n",s[i][3]); } } } return 0; }
1473 There's Treasure Everywhere!
問題概要
原点から8方位に、距離1ずつ何度か移動する。
移動が文字列により与えられたとき、最終地点の座標および原点からの距離を求めよ。
解法
x座標とy座標を足してけばよい。んだけど文字列のパーズが面倒。
ソースコード
なんか汚い。
char in[201]; int main() { int cs=0; while(gets(in),in[0]!='E') { for(int i=0;in[i];i++)if(!isdigit(in[i])&& in[i]!='N'&&in[i]!='E'&&in[i]!='S'&&in[i]!='W') in[i]=' '; double x=0,y=0; for(int c=0;in[c];c++) { double d=0; char dir[3]={0}; for(;in[c]&&in[c]!='N'&&in[c]!='E'&&in[c]!='S'&&in[c]!='W';c++) d*=10,d+=in[c]-'0'; for(int i=0;in[c]&&in[c]!=' ';i++,c++)dir[i]=in[c]; int dx=0,dy=0; for(int i=0;dir[i];i++) { if(dir[i]=='N')dy=1; if(dir[i]=='S')dy=-1; if(dir[i]=='W')dx=-1; if(dir[i]=='E')dx=1; } if(dy==0&&dx==0)break; d/=hypot(dx,dy); x+=dx*d; y+=dy*d; } printf("Map #%d\n",++cs); printf("The treasure is located at (%.3f,%.3f).\n",x,y); printf("The distance to the treasure is %.3f.\n\n",hypot(x,y)); } return 0; }