POJ 1759: Garland
概要
N個の提灯が下がった糸を垂らす.片方の端点はAとし,他方をBとする.
このとき,糸は重力にしたがって垂れ下がる.
i番目の提灯の高さをH_iとすると,
H_1 = A, H_N = B,
H_i = (H_(i-1) + H_(i+1)) - 1
が成り立つ.
あるAが与えられたとき,全ての提灯の高さを0以上にするにはBを最小でいくつにすればよいか.
解法
Bの位置について二分探索...が差分方程式を解かないといけない.
とすると,与式をいじいじして,
を得る.したがって,
H_1 = A,H_N = Bより,が求められる.
他の人のを見ると,H_2について二分探索している.確かにこちらの方が簡単だ.
int N; double A; int main(){ scanf("%d%lf\n", &N, &A); double lb = 0, ub = 1000000; for(int k = 0; k < 100; ++k){ double mid = (lb + ub) / 2; double d1 = (mid - A) / (N - 1) - (N - 1); bool f = true; for(int i = 1; i < N; ++i){ double p = A + (i-1)*d1 + (i-1)*(i-1); f = f && p >= 0; } if(f){ ub = mid; } else{ lb = mid; } } printf("%.2f\n", ub); return 0; }
POJ 3685: Matrix
概要
N次正方行列があって,その(i,j)-成分はi^2+100000*i+j^2-100000*j+i*jで与えられる.
M番目に小さい要素の値はいくつか.
解法
解について二分探索.列を固定してしまえば,値は行について単調増加であることが式からわかる.
2次方程式を解けばO(1)で計算できるのだろうけど,ここでも二分探索を使った.
#include <cstdio> #include <algorithm> typedef long long ll; const ll MAX_N = 50010; ll N, M; ll func(ll i, ll j){ return i * i + 100000 * i + j * j - 100000 * j + i * j; } bool C(ll x){ ll cnt = 0; for(int j = 1; j <= N; ++j){ ll lb = 0, ub = N + 1; while(ub - lb > 1){ ll mid = (lb + ub) / 2; if(func(mid, j) < x){ lb = mid; } else{ ub = mid; } } cnt += lb; } // printf(">>%lld\n", cnt); return cnt < M; } main(){ int T; scanf("%d", &T); for(;T>0;--T){ scanf("%lld%lld", &N, &M); ll ub = MAX_N * MAX_N * 3 + MAX_N * 100000 * 2; ll lb = -ub; while(ub - lb > 1){ ll mid = (lb + ub) / 2; // printf("(%lld)\n", mid); if(C(mid)){ lb = mid; } else{ ub = mid; } } printf("%lld\n", lb); } return 0; }
POJ 3662: Telephone Lines
概要
N頂点,辺P本のグラフが与えられる.K本の辺は無料で使うことができ,それ以上使うとK本からはみ出た辺の中での最大の長さだけの料金が発生する.
頂点1から頂点Nまでの経路をできるだけ安い料金で実現したい.
解法
料金(辺の長さの最大値)について二分探索.料金をdと仮定したとき,dより長い辺をK本以下使って1からNへの経路をつくれるかを判定する.判定にはDijkstraを利用.
typedef int Weight; struct Edge{ int from, to; Weight weight; Edge(int f, int t, Weight w) : from(f), to(t), weight(w) { } }; bool operator<(const Edge &a, const Edge &b){ return a.weight > b.weight; } typedef vector<Edge> Edges; typedef vector<Edges> Graph; const int INF = 1000000; int N, P, K; int d[1010]; bool visited[1010]; bool C(Graph &g, int L) { priority_queue<Edge> Q; for(int i = 0; i < N; ++i) d[i] = INF; memset(visited, 0, sizeof(visited)); d[0] = 0; Q.push(Edge(-1, 0, 0)); while(!Q.empty()){ int v = Q.top().to; int cnt = Q.top().weight; Q.pop(); if(visited[v]) continue; visited[v] = true; if(v == N-1) return cnt <= K; EACH(i, g[v]){ int ncnt = cnt; if(i->weight > L) ncnt++; if(d[i->to] > ncnt){ d[i->to] = ncnt; Q.push(Edge(i->from, i->to, ncnt)); } } } return false; } main(){ scanf("%d%d%d", &N, &P, &K); Graph g(N); for(int i = 0; i < P; ++i){ int a, b, l; scanf("%d%d%d", &a, &b, &l); --a; --b; g[a].push_back(Edge(a, b, l)); g[b].push_back(Edge(b, a, l)); } int lb = -1, ub = 1000010; while(ub - lb > 1){ int mid = (lb + ub) / 2; // printf(">%d\n", mid); if(C(g, mid)){ ub = mid; } else{ lb = mid; } } printf("%d\n", ub > 1000000 ? -1: ub); }
POJ 2991: Crane
何を血迷ったのか,くっそ忙しいときに取り組んだ.そのうえ,勘違いをただすためにかなり時間を食った.今は反省している.
問題文・解法は蟻本まんまのため省略.
多分同じ間違いをする人はいないと思うが,もしも同じ勘違いで頭を悩ませる人が出てきたときのために勘違いしてたことを書く.
疑問:「回転する角度って,累積していっていいの?」
問題ない.というか,普通の人にはこれを勘違いする理由が見当たらないかもしれない.
クレーンの座標の導出式は,
だと思っていた.ただし,i番目のクレーンの回転角度を,をだけ回転させる行列,とする.
この式はまちがっていて,正しくは
もうちょっとかっこよくすると,
なぜならば,i番目のクレーンを回転させると,それ以外のクレーンも(原点は違うが)同じ角度の分だけ回転されるから.
i番目のクレーンから見た座標系は,i-1番目のクレーンの座標系に乗っていると考えたらすっきり納得できた.
今では,なぜ勘違いをしていたのかわからない...
POJ 3579: Median
概要
N個の配列が与えられる.この中から2つの要素を取り出し,その差を計算し別の数列とする.
新しく作った配列の中央値を探索する.ただし,この問題において偶数長(m)の配列の中央値はm/2番目に小さい値と定義する.
解法
解を仮定して二分探索.
入力された配列はソートする.(絶対値をとっていて,順番には左右されないので)
そうすることで,個数のカウントが二分探索を使って行える.
制限時間がかなり厳しい.
int N; int x[100010]; ll M; bool C(int mid){ ll cnt = 0; for(int i = 0; i < N; ++i){ cnt += lower_bound(x + i + 1, x + N, x[i] + mid) - (x + (i + 1)); } return cnt < M; } int main(){ while(~scanf("%d", &N)){ for(int i = 0; i < N; ++i) scanf("%d", x+i); M = (ll)N * (N - 1) / 2; if(M % 2) M = M / 2 + 1; else M = M / 2; sort(x, x+N); int lb = 0, ub = 1000000010; while(ub - lb > 1){ int mid = (lb + ub) / 2; if(C(mid)){ lb = mid; } else{ ub = mid; } } printf("%d\n", lb); } return 0; }
POJ 3111: K Best
概要
n個の宝石があって,k個を残して売り払いたい.その時,残したk個の宝石の単位重さあたりの評価値を最大化したい.
解法
蟻本そのままで二分探索.謎WA連発の末,探索終えた後にもう一回答えを計算したほうがいいという結論に至る.
繰り返し回数が結構シビアかもしれない.
int n, k; int v[100010], w[100010]; pair<double, int> y[100010]; bool C(double x){ for(int i = 0; i < n; ++i) y[i] = make_pair(v[i] - w[i] * x, i + 1); sort(y, y + n); double sum = 0; for(int i = 0; i < k; ++i){ sum += y[n-i-1].first; } return sum >= 0; } int main(){ scanf("%d%d", &n, &k); for(int i = 0; i < n; ++i){ scanf("%d%d", &v[i], &w[i]); } double lb = 0, ub = 1e8; for(int i = 0; i < 50; ++i){ double mid = (lb + ub) / 2; if(C(mid)){ lb = mid; } else{ ub = mid; } } C(lb); for(int i = 0; i < k; ++i) printf("%d%c", y[n-i-1].second, i == k - 1 ? '\n': ' '); return 0; }
POJ 2976: Dropping Tests
概要
授業でn回のテストを受けた.i番目のテストではb[i]問中a[i]問正解した.
受けたテストのうちk回を取り除き,を最大化したい.
解法
二分探索.蟻本の練習問題まんま.
int n, k; int a[1010], b[1010]; double v[1010]; bool C(double x){ for(int i = 0; i < n; ++i) v[i] = 100LL * a[i] - x * b[i]; sort(v, v + n); double sum = 0; for(int i = k; i < n; ++i){ sum += v[i]; } return sum >= 0; } main(){ for(;;){ scanf("%d%d", &n, &k); if( n == 0 && k == 0 ) return 0; for(int i = 0; i < n; ++i){ scanf("%d", &a[i]); } for(int i = 0; i < n; ++i){ scanf("%d", &b[i]); } double lb = 0, ub = 100; for(int i = 0; i < 100; ++i){ double mid = (lb + ub) / 2; if(C(mid)){ lb = mid; } else{ ub = mid; } } printf("%d\n", (int)floor(lb + 0.5)); } }