Codeforces #421 (Div. 1) B: Mister B and PR Shifts
まず初期状態のdeviationを計算しておく。
1回のcyclic shiftでdeviationの値がどのように変化するかを知りたい。
基本的には
p[i] < iのときp[i]の位置はiに近づくので、そのような値はdeviationを1だけ減らす。
p[i]>=iのときp[i]の位置はiから離れるので、そのような値はdeviationを1だけ増やす。
なので、次のcyclic shiftによってdeviationを減らす値と増やす値を数えておく。仮にそれぞれx, yとする。それによって毎ターンdeviationを増減させる。
ただし、この方針では右端が例外になる。
i(i>1)番目にある値はn-i回目のシフトで右端から左端へ移動する。このときだけ、deviationの計算をうまく分ける。
すなわち値zについて考える場合
deviationから|n-z|を引いて|1-z|を足す。また、このときx, yが変わる場合があるので注意。上図では
3は3から離れる => 3は3に近づく
というふうに状態が変化している。すなわち、
x++, y--
となる。
x, yの変化はもう1種類あって
のようにp[i]=iになるような場合で
x--, y++となる。
ただし、
のような場合に注意。
コード
Submission #28107263 - Codeforces
SRM 716 DIV1 450: JumpDistancesOnTree
まず与えられた木の全点対の距離を計算しておく。
これを
d[u][v]: (u, v)間の距離
としておく。
d[u][v] ∈ Sかつu!=v であるような辺(u, v)をすべて持つようなグラフをHとする。このグラフでスタート地点の頂点0から探索して到達可能な頂点の集合をVとする。H上でv∈Vに到達できるということは、与えられた木の上での移動距離d[i]がすべてSに属することと同じ。ただし、Sの元うちどれを使っていて、どれを使っていないかは不明。ここで頂点w∈Vを
d[w][v] = Sの最大値
であるような頂点vが存在するような頂点とする。
もしそのような頂点が存在するならば、Sの元をすべて使うことができる。逆に、そうでなければ駄目なのは自明。
S[i]<S[i+1]
が成り立つのであった。
よってS[i+1]の移動ができると仮定すると、その移動とは逆方向にS[i]だけ移動することができる。したがってSの最大値の移動ができるならば、Sに属する距離になるようなすべての移動ができる。
SRM 716 DIV1 250: ConstructLCS
ab<=bc<=caを仮定すると下の図のように解を構成できる。
実際はab, bc, caの大小関係はこのとおりとは限らない。なので、ソートしてから3個の文字列を作ってみる。あとはこの3個の文字列がa, b, cのどれに対応するかを全部試して、実際にLCSを計算してみればいい。
AOJ 2541: Magical Bridges
magical bridgeの数はたかだか100個しかないので、使うmagical bridgeの数も100個以下である。
di[v][a]: magical bridgeをa回使ったときのSiからvの最短距離。ただし、magical bridgeの距離は0
とする。
ダイクストラ法によりd1とd2を事前に計算しておく。
xをmagical bridgeの距離とする。xは0以上の整数を自由にとる。
仮に、xを固定したとして考えてみる。このとき、
S1からTの最短距離は min[0<=a<101](a*x + d1[T][a])
S2からTの最短距離は min[0<=a<101](a*x + d2[T][a])
である。
要するにそれぞれの直線の集合から最小値を計算している。
そしてその差を最小にしたいのだった。
もしもxが非負の実数であったならば、最短距離の差を最小にするようなxの候補は、すべての直線のペアの交点の座標のいずれかである。しかし、交点は非負の整数でなければならないので、もし交点座標xが整数でなければ、xの代わりにfloor(x)とceil(x)を見る。
この解法に1つ例外があって、すべての直線のペアに交点がない場合である。このとき最適なxの値は任意。
// dis, pos, magical using P = tuple<ll, int, int>; const int X = 101; const ll INF = (ll)1e12; int N, M, S1, S2, T; ll d1[1001][X], d2[1001][X]; // to, weight vector<pii> G[1001]; void dijk(int S, ll d[1001][X]) { rep(i, N)rep(j, X)d[i][j] = INF; priority_queue<P, vector<P>, greater<P> > Q; Q.push(P(0, S, 0)); while (sz(Q)) { ll di; int u, x; tie(di, u, x) = Q.top(); Q.pop(); if (d[u][x] < di)continue; d[u][x] = di; if (u == T)continue; each(e, G[u]) { ll ndi = di; int v, w, y = x; tie(v, w) = e; if (w != -1) { ndi += w; } else { y++; } if (y<X && d[v][y] > ndi) { Q.push(P(ndi, v, y)); } } } } vll points(ll c1[1001][X], ll c2[1001][X]) { vll res; rep(a1, X)rep(a2, X)if (a1 != a2){ ll b1 = c1[T][a1], b2 = c2[T][a2]; ll den = a1 - a2; ll num = b2 - b1; if (num%den) { res.push_back(num / den + 1); } res.push_back(num / den); while (sz(res) && res.back() < 0)res.pop_back(); } return res; } using L = pair<ll, ll>; vector<L> lines(ll d[1001][X]) { vector<L> res; rep(i, X)if (d[T][i] != INF) { res.emplace_back(i, d[T][i]); } return res; } ll calcMin(vector<L> &ls, ll x) { ll res = LLONG_MAX; each(l, ls) { smin(res, l.first*x + l.second); } return res; } int main(){ ios::sync_with_stdio(false); cin.tie(0); while (cin >> N >> M >> S1 >> S2 >> T, N) { --S1; --S2; --T; rep(i, N)G[i].clear(); rep(i, M) { int u, v; string a; cin >> u >> v >> a; --u; --v; int w = a != "x" ? stoi(a) : -1; G[u].emplace_back(v, w); G[v].emplace_back(u, w); } dijk(S1, d1); dijk(S2, d2); vll xs = { 1 }, p11, p12, p22; p11 = points(d1, d1); p12 = points(d1, d2); p22 = points(d2, d2); xs.insert(xs.end(), all(p11)); xs.insert(xs.end(), all(p12)); xs.insert(xs.end(), all(p22)); sort(all(xs)); xs.erase(unique(all(xs)), end(xs)); auto l1 = lines(d1), l2 = lines(d2); ll ans = LLONG_MAX; each(x, xs) { ll m1 = calcMin(l1, x), m2 = calcMin(l2, x); smin(ans, abs(m1 - m2)); } cout << ans << endl; } }
Triplet Min Sum | CS Academy #30 (Div. 2 only)
実は解候補はlca(A,B), lca(B,C), lca(C, A)の3通りしかない。
与えられた木を根付き木とし、簡単のため
depth(A) <= depth(B) <= depth(C)
とする。Aを考慮しなければ、下のような図が描ける。
(1)Aがパス(B,C)上にある場合
明らかにAが解になる。
(2)Aがパス(B,C)上にない場合
Aは上図の囲われていない部分のいずれかにある。Aパス(B,C)上にないので、Aからパス(B,C)に到達するのに必ず通らなければならないある(B,C)上のある1点が存在する。この点は
lca(A,B)またはlca(B,C)またはlca(C,A)
である。
Prefix Free Subset | CS Academy #30 (Div. 2 only)
文字列a,ab,abc,abdが与えられているとする。上図のようなtrieを描いてみよう。
簡単のため、文字列の最大の長さは考えないでみる。
K=1とすると明らかにaだけを使えばいい。
では、K=2の場合は?
K=1のときと同じようにaを使おうとすると、うまくいかない。aの子孫にあたる単語はすべてaをprefixとして含むため使えない。つまり、ab,abc,abdはすべて使えない。よってaは使わない。下図。
今度はabを使おうとすると、同様にabc,abdを使えない。これもK=2に届かない。
こうしてtrieを根から見ていくと、最終的に{abc, abd}を得る。
結局、自分を含まない先祖にあたるprefixを使ってない場合、ある単語について2通りの選択がある。
(1)その単語を使う
この場合はその子孫にあたる、その単語をprefixとして含む単語は使えない
(2)その単語を使わない
この場合はその子孫に当たる、その単語をprefixとして含む単語を使える。
(1),(2)を再帰でうまく処理する。最も長い単語の長さを二分探索で固定すれば、全体で
O(max(|S[i]|)*Σ|S[j]|)
の実行時間で間に合う。
雑記
trieを使うのすぐに気付けなかった。ライブラリは持っておくべき。ちなみに、trieのことをprefix treeとも呼ぶらしく、解法を連想しやすいかも。
Constant Sum | CS Academy #30 (Div. 2 only)
更新の操作は以下のように表せる。
A[i]+=s
A[j]+=-s/(N-1), j!=i
前者を変形すれば
A[i]+= (s+s/(N-1)) -s/(N-1)
となる。なので
全体に-s/(N-1)を加算し、A[i]にs+s/(N-1)を加算したとすれば、1クエリO(1)で処理できる。