PKU演習問メモ(8/27)
No. | 問題名 | 問題の種類および解法 | 難易度 |
---|---|---|---|
2553 | The Bottom of a Graph | 強連結成分分解 | ★★★☆☆ |
2437 | Muddy roads | 貪欲法 | ★★☆☆☆ |
2457 | Part Acquisition | 幅優先探索・経路出力 | ★★★☆☆ |
2451 | Uyuw's Concert | 幾何・多角形の切断 | ★★★☆☆ |
2491 | Scavenger Hunt | グラフ理論 | ★★☆☆☆ |
2484 | A Funny Game | ゲーム | ★★★☆☆ |
2553 The Bottom of a Graph
問題概要
有向グラフにおいて、ある頂点uから出発してたどり着ける全ての頂点vについて、v→uへの道があるような頂点uを"Bottom"と呼ぶことにする。
与えられたグラフの"Bottom"を全て出力せよ。
与えられるグラフの頂点の数は5000以下である。
解法
頂点uが"Bottom"であるとき、uの強連結成分は全て"Bottom"である。
強連結成分分解後にできる森のグラフにおいて"Bottom"は葉のノードであるため、
葉のノード(を構成する連結成分の頂点)を全て出力すればよい。
ソースコード
int main() { int v,e; while(scanf("%d",&v),v) { scanf("%d",&e); Graph g(v); rep(i,e) { int a,b; scanf("%d%d",&a,&b); a--; b--; g[a].pb(Edge(a,b,1)); } vector<vi> scc; stronglyConnectedComponents(g,scc); int p[5001]; rep(i,v)p[i]=i; fr(i,scc) { int r=(*i)[0]; fr(j,(*i))p[*j]=r; } vi ans; fr(i,scc) { fr(j,(*i))fr(k,g[*j]) if(p[k->dst]!=(*i)[0])goto SKIP; fr(j,(*i))ans.pb(*j); SKIP:; } sort(all(ans)); fr(i,ans)printf("%d%c",*i+1,i==ans.end()-1?'\n':' '); } return 0; }
2437 Muddy roads
問題概要
数直線上にN個の泥の区間がある。
泥の区間は始点siと終点eiにより与えられる。
この泥の区間を長さLの板で全て覆いたい。板同士は重なってもよく、板が泥のない区間へはみ出してもよいとするとき、必要な板の枚数を求めよ。
N≦10000を満たす。
解法
区間をソートして、貪欲に板を敷いていけばよい。
整数の切り上げに若干注意が必要。
ソースコード
int n,l; pi mud[10000]; int main() { scanf("%d%d",&n,&l); rep(i,n) { int a,b; scanf("%d%d",&a,&b); mud[i]=mp(a,b); } sort(mud,mud+n); int p=-1,ans=0; rep(i,n) { while(i<n&&mud[i].second<p)i++; if(i==n)break; p=max(p,mud[i].first); int k=(mud[i].second-p)/l+((mud[i].second-p)%l!=0); p+=k*l; ans+=k; } printf("%d\n",ans); return 0; }
2457 Part Acquisition
問題概要
1番のアイテムを、(1対1の)物々交換を繰り返してK番のアイテムにしたい。
物々交換の可能なリストが与えられるとき、最小の交換回数でK番のアイテムを得るための交換の順序を出力せよ。
そのような手順が複数ある場合どれを出力してもよい。手順が存在しない場合は-1を出力せよ。
K≦1000を満たす。
解法
物々交換のリストは有向グラフとみることができ、問題文で求められているのは1番のノードからK番のノードまでの最短経路を出力することに相当する。
幅優先探索で経路を求め、バックトラックにより経路を出力する。
ソースコード
int n,k,node[1001],p[1001]; pi e[50000]; int main() { scanf("%d%d",&n,&k); int ei=0,tmp; rep(i,n) { int a,b; scanf("%d%d",&a,&b); e[++ei]=mp(b,node[a]); node[a]=ei; } p[1]=1; queue<int> Q; Q.push(1); while(!Q.empty()) { int c=Q.front(); Q.pop(); if(c==k)break; for(int i=node[c];i;i=e[i].second)if(!p[e[i].first]) { p[e[i].first]=c; Q.push(e[i].first); } } if(p[k]==0) { puts("-1"); return 0; } int ans[1001],l=0; for(int i=k;i!=1;i=p[i])ans[l++]=i; ans[l++]=1; printf("%d\n",l); rep(i,l)printf("%d\n",ans[l-1-i]); return 0; }
2451 Uyuw's Concert
問題概要
4頂点の座標が(0,0),(10000,0),(10000,10000),(0,10000)であるような正方形の広場がある。
ここに線分で表されるベンチがいくつかある。線分は正方形の辺と辺を結ぶ。
これらのベンチのどこに座っても見えるようなステージを作りたい。
そのようなステージの最大の面積を求めよ。
解法
カーゴ・カルト。
(多角形の切断+多角形の面積のライブラリ使用)
ソースコード
int main() { int n; scanf("%d",&n); G g; g.pb(P(0,0)); g.pb(P(10000,0)); g.pb(P(10000,10000)); g.pb(P(0,10000)); rep(i,n) { double a,b,c,d; scanf("%lf%lf%lf%lf",&a,&b,&c,&d); g=convex_cut(g,L(P(a,b),P(c,d))); } printf("%.1f\n",area2(g)/2); return 0; }
2491 Scavenger Hunt
問題概要
S個の地点があり、S-1個の"地点の名前 次の地点の名前"の関係が与えられる。
このとき、できるだけ長い地点の名前の列を作れ。
S≦333を満たす。
解法
関係を有向グラフと見ると、分岐のない木になる。
根からdfsすればよい。
ソースコード
struct S{ char s[100]; bool operator<(const S &r)const{ return strcmp(s,r.s)<0; } }; S pl[350]; int n,in[350]; bool e[350][350]; void dfs(int cur) { puts(pl[cur].s); rep(i,n)if(e[cur][i]) { dfs(i); break; } } int main() { int CS; scanf("%d",&CS); rep(cs,CS) { scanf("%d",&n); rep(i,n)in[i]=0; rep(i,n)rep(j,n)e[i][j]=0; map<S,int> id; int nid=0; S t1,t2; rep(i,n-1) { scanf("%s%s",t1.s,t2.s); if(!id.count(t1)) { pl[nid]=t1; id[t1]=nid++; } if(!id.count(t2)) { pl[nid]=t2; id[t2]=nid++; } e[id[t1]][id[t2]]=1; in[id[t2]]++; } printf("Scenario #%d:\n",cs+1); rep(i,n)if(in[i]==0) { dfs(i); break; } puts(""); } return 0; }
2484 A Funny Game
問題概要
n枚のコインを円状に並べ、二人で次のようなゲームを行う。
連続する1枚または2枚のコインを取っていき、最後にコイン取ったプレイヤーが勝ちとなる。
お互いが最善を尽くすとするとき、ゲームの勝者を求めよ。
解法
コインが円形に並んでいる状態は考えにくいので、その次の状態から考える。
すると、山が1個ある石取りゲームのようになる。
プレイヤーは山から1個または2個石をとり、山を適当なところで分割させることもできる。
このときどのプレイヤーが勝つかというと、
- 先手必勝の山・後手必勝の山に分けられるとき→後手必勝の山でわざと負けて、先手番で先手必勝の山で勝てる
- 先手必勝の山二つに分けられるとき→負ける
- 後手必勝の山二つに分けられるとき→どっちかの山で好きに勝敗を決められるので勝てる
となるので、これをプログラムにして1000くらいまで実行させてみると、n≧3で常に後手必勝となることがわかる。
ソースコード
int main() { int n; while(scanf("%d",&n),n)puts(n==1||n==2?"Alice":"Bob"); return 0; } /* bool ans[1000]; int main() { ans[1]=ans[2]=1; for(int i=3;i<1000;i++) { for(int l=1;l<3;l++)rep(j,i-l+1) { int a=j,b=i-j-l+1; if(!ans[a]&&!ans[b]) { ans[i]=1; goto NEXT; } } NEXT:; } int n; while(scanf("%d",&n),n)puts(n<3||!ans[n-1]||!ans[n-2]?"Alice":"Bob"); return 0; } */