PKU演習問メモ(9/1)
復活きたあああああ
解説は風呂にでも入った後で書く。←翌日になってようやく書き終えた
No. | 問題名 | 問題の種類および解法 | 難易度 |
---|---|---|---|
2654 | Rock-Paper-Scissors Tournament | 実装 | ★☆☆☆☆ |
2626 | Chess | 動的計画法or最小費用流 | ★★☆☆☆ |
2640 | Playground | 枝刈り付き探索 | ★★★☆☆ |
2661 | Factstone Benchmark | 対数 | ★★☆☆☆ |
2610 | Dog & Gopher | 幾何 | ★★☆☆☆ |
2632 | Crashing Robots | シミュレーション | ★★☆☆☆ |
2603 | Brave balloonists | 約数の個数 | ★★☆☆☆ |
1013 | Counterfeit Dollar | 探索 | ★★☆☆☆ |
2654 Rock-Paper-Scissors Tournament
問題概要
n人がじゃんけんの総当りの試合をk回行う(全体で試合はn*(n-1)/2*k回)
各人の出した手は全て与えられる。
勝率をw/(l+w)とするとき、(wは勝った回数、lは負けた回数で、あいこはどちらにも数えない)各人の勝率を出力せよ。
勝率が定義できない場合は"-"を出力せよ。
n≦100を満たす。
解法
じゃんけんの判定を行って勝ち数と負け数を数えればよい。
ソースコード
int n,k; int win[101],lose[101]; int main() { while(scanf("%d",&n),n) { scanf("%d",&k); rep(i,n+1)win[i]=lose[i]=0; rep(i,n*(n-1)/2*k) { int a,b; char h1[10],h2[10]; scanf("%d%s%d%s",&a,h1,&b,h2); if(h1[0]==h2[0])continue; if(h1[0]=='r'&&h2[0]=='s'||h1[0]=='s'&&h2[0]=='p' ||h1[0]=='p'&&h2[0]=='r')win[a]++,lose[b]++; else win[b]++,lose[a]++; } for(int i=1;i<=n;i++) { if(win[i]+lose[i]==0)puts("-"); else printf("%.3f\n",win[i]/double(win[i]+lose[i])); } puts(""); } return 0; }
2626 Chess
問題概要
n人の中から30人のチェスチームを作る。
それぞれのプレイヤーの先手番での強さおよび後手番での強さが与えられる。
チームにおいて、先手を担当する人間、後手を担当する人間はそれぞれ15人で、途中で先手と後手の担当を入れ替えることはできない。
チーム全体の強さは各人の担当する手番での強さの和となる。
このとき、チーム全体の強さの最大値を求めよ。
n≦1000を満たす。
解法
動的計画法での解法、最小費用流を用いる解法の2通りが考えられた。
DPの場合、dp[i][j][k]にi人まで見て、先手番にj人、後手番にk人割り振ったときのチームの強さをもたせ、表を更新していく。
費用流の解法は、それぞれのプレイヤーと(先手番・後手番担当を表す2つのノード)とを容量1,コスト(プレイヤーのその手番での強さ*-1)の辺で結び、
先手番・後手番担当を表すノードと、シンクをそれぞれ容量15,コスト0の辺で結び、最小費用流を流す。
費用流で解いたらWAだったんだけど何でだろう。
[追記]費用流でもAC. Primal-Dual法はコストが負の辺があるとダメみたい(わかってない
それを回避するために辺のコストに適当に100とかの数字を足して一旦正にして、後で引いて帳尻を合わせる。
費用流使ったの初めて!これで次から費用流も解けるね!
ソースコード
int n,w[1000],b[1000]; int dp[2][16][16]; int main() { while(~scanf("%d%d",w+n,b+n))n++; rep(i,16)rep(j,16)dp[0][i][j]=0; int cur=0,next=1; rep(i,n) { rep(j,16)rep(k,16)dp[next][j][k]=dp[cur][j][k]; rep(j,16)rep(k,16) { if(j<15)dp[next][j+1][k]=max(dp[next][j+1][k],dp[cur][j][k]+w[i]); if(k<15)dp[next][j][k+1]=max(dp[next][j][k+1],dp[cur][j][k]+b[i]); } swap(cur,next); } printf("%d\n",dp[cur][15][15]); return 0; }
spaghetti sourceの最小費用流のライブラリを用いた別解
ライブラリは微妙にいじってある。
struct Edge { int src, dst; Weight capacity,cost; Edge(int src, int dst, Weight capacity=0, Weight cost=0) : src(src), dst(dst), capacity(capacity), cost(cost) { } }; bool operator < (const Edge &e, const Edge &f) { return e.cost != f.cost ? e.cost > f.cost : // !!INVERSE!! e.capacity != f.capacity ? e.capacity > f.capacity : e.src != f.src ? e.src < f.src : e.dst < f.dst; } typedef vector<Edge> Edges; typedef vector<Edges> Graph; typedef vector<Weight> Array; typedef vector<Array> Matrix; #define RESIDUE(u,v) (capacity[u][v] - flow[u][v]) #define RCOST(u,v) (cost[u][v] + h[u] - h[v]) pair<Weight, Weight> minimumCostFlow(const Graph &g, int s, int t) { const int n = g.size(); Matrix capacity(n, Array(n)), cost(n, Array(n)), flow(n, Array(n)); rep(u,n) fr(e,g[u]) { capacity[e->src][e->dst] += e->capacity; cost[e->src][e->dst] += e->cost; } pair<Weight, Weight> total; // (cost, flow) vector<Weight> h(n); for (Weight F = inf; F > 0; ) { // residual flow vector<Weight> d(n, inf); d[s] = 0; vector<int> p(n, -1); priority_queue<Edge> Q; // "e < f" <=> "e.cost > f.cost" for (Q.push(Edge(-2, s)); !Q.empty(); ) { Edge e = Q.top(); Q.pop(); if (p[e.dst] != -1) continue; p[e.dst] = e.src; fr(f, g[e.dst]) if (RESIDUE(f->src, f->dst) > 0) { if (d[f->dst] > d[f->src] + RCOST(f->src, f->dst)) { d[f->dst] = d[f->src] + RCOST(f->src, f->dst); Q.push( Edge(f->src, f->dst, 0, d[f->dst]) ); } } } if (p[t] == -1) break; Weight f = F; for (int u = t; u != s; u = p[u]) f = min(f, RESIDUE(p[u], u)); for (int u = t; u != s; u = p[u]) { total.first += f * cost[p[u]][u]; flow[p[u]][u] += f; flow[u][p[u]] -= f; } F -= f; total.second += f; rep(u, n) h[u] += d[u]; } return total; } void addE(Graph &g,int src,int dst,Weight capacity,Weight cost) { g[src].pb(Edge(src,dst,capacity,cost)); g[dst].pb(Edge(dst,src,0,-cost)); } int main() { int n=0,w[1000],b[1000]; while(~scanf("%d%d",w+n,b+n)) { w[n]=100-w[n]; b[n]=100-b[n]; n++; } Graph g(n+4); rep(i,n) { addE(g,0,i+1,1,0); addE(g,i+1,n+1,1,w[i]); addE(g,i+1,n+2,1,b[i]); } addE(g,n+1,n+3,15,0); addE(g,n+2,n+3,15,0); pi ans=minimumCostFlow(g,0,n+3); printf("%d\n",3000-ans.first); return 0; }
2640 Playground
問題概要
n個の半円の形をしたスチールの棒がある。
それぞれの長さが与えられるとき、弧の端と端をぴったりとくっつけ、輪を作ることが出来るかを答えよ。使わない弧があってもかまわない。
それぞれの弧の長さは小数であり、n≦20を満たす。
解法
半円の弧を、その両端を結ぶ線分の棒と考えてやる。
そして、三角不等式を満たすペアを探すよう深さ優先探索する。
枝刈りをしないとTLEだが、以下のような枝刈りをすれば30msくらいで通る。
- 三角形の辺の長さを限定する(a≦b≦cのようなものだけ考える)
- 残りの全ての棒を短い辺につないでもa+b≧cにならなかったらすぐに探索を打ち切る
ソースコード
int k; double l[20]; bool dfs(int cur,double a,double b,double c) { if(a>b||b>c)return 0; if(c>0&&a+b>=c)return 1; if(cur==k)return 0; double d=a+b-c; for(int i=cur;i<k;i++)d+=l[i]; if(d<0)return 0; //brunch-cut if( dfs(cur+1,a+l[cur],b,c)|| dfs(cur+1,a,b+l[cur],c)|| dfs(cur+1,a,b,c+l[cur])|| dfs(cur+1,a,b,c) )return 1; return 0; } int main() { while(scanf("%d",&k),k) { rep(i,k)scanf("%lf",l+i); sort(l,l+k,greater<double>()); puts(dfs(0,0,0,0)?"YES":"NO"); } return 0; }
2661 Factstone Benchmark
問題概要
Amtel社は2010年に128bitのCPUを発表した。CPUは10年毎にリリースされ、bitは10年毎に2倍になる。
(2020年は256bit,逆に2000年時点では64bit……)
CPUで表せる最大の整数の階乗n!としたとき、nをFactstone Benchmarkと呼ぶ。
年yが与えられたとき、その年でのCPUのFactstone Benchmarkを求めよ。
1960≦y≦2160を満たす。
解法
まず年度でのbitを求める。これはdouble型に収まる。
収まる整数は2^bit乗なので、それ以下となるn!を、logを取り探す。
すなわち、bit≦log n! / log 2を満たすnを、log kを順次足していき見つければよい。
long doubleを一応つけてみたけど不要っぽい。
ソースコード
int main() { int n; while(scanf("%d",&n),n) { n/=10; n-=194; ld bit=pow(2,(ld)1.0*n)*log((ld)2.0); int ans=1; while(bit>0)bit-=log(1.0*++ans); printf("%d\n",ans-1); } return 0; }
2610 Dog & Gopher
問題概要
犬とリスが一匹ずつおり、それぞれの初期座標が与えられる。
座標平面にはいくつかの穴があり、犬はリスを追いかけ、リスは穴に逃げる。
犬の速度はリスの速度の二倍である。
リスが犬から逃げて穴に先に入れるかを判定せよ。
解法
それぞれの穴について、リスからの距離と犬からの距離を求め、リスが犬より先につけるかを判定すればよい。
ソースコード
int main() { double gx,gy,dx,dy,ax=INF,ay=INF,x,y; scanf("%lf%lf%lf%lf",&gx,&gy,&dx,&dy); while(~scanf("%lf%lf",&x,&y)) { if(ax!=INF)continue; if(hypot(gx-x,gy-y)*2<=hypot(dx-x,dy-y))ax=x,ay=y; } if(ax==INF)puts("The gopher cannot escape."); else printf("The gopher can escape through the hole at (%.3f,%.3f).\n", ax,ay); return 0; }
2632 Crashing Robots
問題概要
AxBのグリッドであらわされる部屋にn個のロボットがあり、それにm個の命令を出す。
命令は"ロボットの番号 方向 回数"の形式で与えられ、一つの命令が完全に実行され終えた後で次の命令が実行される。
グリッドにはロボットが最大一つしか入ることができず、同じグリッドにロボットが二つ入ろうとするとクラッシュする。また、ロボットは壁にぶつかる指示を出されてもクラッシュする。
m個の命令をクラッシュせず実行できるなら"OK"を、クラッシュするなら最初のクラッシュを出力せよ。
解法
シミュレートすればよい。
座標系がやっかいなので要注意。
ソースコード
int dy[]={-1,0,1,0},dx[]={0,-1,0,1}; int h,w,m,n; int rx[100],ry[100],rd[100]; int r[100][100]; int main() { int cs; scanf("%d",&cs); while(cs--) { scanf("%d%d%d%d",&w,&h,&n,&m); rep(i,h)rep(j,w)r[i][j]=-1; rep(i,n) { char d; scanf("%d%d %c",rx+i,ry+i,&d); ry[i]=h-ry[i]; rx[i]--; r[ry[i]][rx[i]]=i; rd[i]=d=='N'?0:d=='S'?2:d=='E'?3:1; } int crush=-1,target=-1; rep(i,m) { int no,rp; char ac; scanf("%d %c%d",&no,&ac,&rp); no--; if(crush!=-1)continue; if(ac=='F') { r[ry[no]][rx[no]]=-1; while(rp--) { ry[no]+=dy[rd[no]]; rx[no]+=dx[rd[no]]; if(ry[no]<0||ry[no]>=h||rx[no]<0||rx[no]>=w) { crush=no; break; } if(r[ry[no]][rx[no]]!=-1) { crush=no; target=r[ry[no]][rx[no]]; break; } } r[ry[no]][rx[no]]=no; } else { rd[no]+=(ac=='L'?1:3)*rp; rd[no]%=4; } } if(crush==-1)puts("OK"); else { printf("Robot %d crashes into ",crush+1); if(target==-1)puts("the wall"); else printf("robot %d\n",target+1); } } return 0; }
2603 Brave balloonists
問題概要
10個の自然数a1,a2,a3……,a10が与えられる。
積a1a2a3……a10の約数の個数の下一桁を求めよ。
解法
ある数のAの約数の個数を求めるには定石どおり素因数分解して
A=p1^q1 p2^p2……
とおけば(1+q1)*(1+q2)*……
である。これをmod10で出力すればよい。
ソースコード
int cnt[10000]; void calc(int a) { for(int i=2;i*i<=a;i++)if(a%i==0) { int c=0; while(a%i==0)a/=i,c++; cnt[i]+=c; } if(a>1)cnt[a]++; } int main() { int a; rep(i,10)scanf("%d",&a),calc(a); int ans=1; rep(i,10000)ans=ans*(cnt[i]+1)%10; printf("%d\n",ans); return 0; }
1013 Counterfeit Dollar
問題概要
12個の見た目が同じコインの中に1つだけ重さの違う偽物のコインが混ざっている。
天秤を3回使ってそれらを量った結果が与えられるとき、偽物のコインを求めよ。
それぞれのコインは英大文字一文字A-Lで表され、天秤の測定結果は
ABCD EFGH even
のように与えられる。
また、測定結果から偽物のコインが必ず一意に定まるとしてよい。
解法
偽物のコインはA-Lで重いor軽いの24通り。なのでそれらを全て、測定結果と矛盾しないか試せばよい。
気づいたら無駄にビット演算を使ってた。
ソースコード
int main() { int cs; scanf("%d",&cs); while(cs--) { int isl[3]={0},isr[3]={0},re[3]={0}; rep(i,3) { char l[7],r[7],res[7]; scanf("%s%s%s",l,r,res); for(int j=0;l[j];j++)isl[i]|=1<<l[j]-'A'; for(int j=0;r[j];j++)isr[i]|=1<<r[j]-'A'; if(res[0]=='u')re[i]=1; else if(res[0]=='d')re[i]=-1; } int ans=0,w; for(;ans<12;ans++)for(w=-1;w<2;w+=2) { rep(i,3) { int s=0; if(isl[i]&1<<ans)s+=w; if(isr[i]&1<<ans)s-=w; if(s!=re[i])goto FAIL; } goto END; FAIL:; } END: printf("%c is the counterfeit coin and it is %s.\n", ans+'A',w>0?"heavy":"light"); } return 0; }