HackerRank Week of Code 33 参加日記
262位/11880人。Easy問は省略する。
n種類の文字で構成された、長さmの文字列sがある。ここで、Transform[x,y]は、文字xをyへ、または文字yをxへ変換できることを意味する。k個のTransformが与えられたとき、sを変換してなるべく長いPalindromic Subsequenceの長さを求めよ。
同じTransformグループの文字を、UnionFindを使ってすべて同じ文字に変換する。そして文字列の最長Palindromic Subsequenceを求めればよい。これはDPの有名問題。
Longest palindromic subsequence - PEGWiki
public static int GetAns(List<int[]> transforms, int[] s) { Transform(s, transforms); return GetLPS(s); } // Change letters in the same group to the same letter public static void Transform(int[] s, List<int[]> transforms) { var unionFind = new UnionFind(); foreach (var t in transforms) { unionFind.Unite(t[0], t[1]); } for (int i = 0; i < s.Length; i++) { s[i] = (int)unionFind.RootValue(s[i]); } } // Longest Palindromic Subsequence: O(N^2) public static int GetLPS(int[] s) { var dp = new int[s.Length + 1, s.Length + 1]; for (int i = s.Length; i >= 0; i--) { for (int j = i; j < s.Length + 1; j++) { if (i == j) dp[i, j] = 0; if (j - i == 1) dp[i, j] = 1; if (j - i > 1 && s[i] != s[j - 1]) dp[i, j] = Math.Max(dp[i + 1, j], dp[i, j - 1]); if (j - i > 1 && s[i] == s[j - 1]) dp[i, j] = 2 + dp[i + 1, j - 1]; } } return dp[0, s.Length]; }
(UnionFindの実装は省略)
n*m座標のグリッドそれぞれに、0~9の数字が入っている。任意の四角形を取り出したとき、中にある数字を並び替えて回文を作りたい。面積が最大になるように四角形を選び出せ。回文は前ゼロを含んではならない。また、面積1の四角形はいずれも有効とする。
1 <= n, m <= 10^5
n * m <= 10^5
ある範囲で回文ができるかどうかは、
・すべての文字種が偶数個である
・または1つの文字種だけが奇数個である
で判断できる。文字の種類は10個しかないので、その偶奇を10ビットで管理する。
if (i > 0) hash_sum[i][j] = hash_sum[i - 1][j]; if (j > 0) hash_sum[i][j] ^= hash_sum[i][j - 1]; if (i > 0 && j > 0) hash_sum[i][j] ^= hash_sum[i - 1][j - 1]; hash_sum[i][j] ^= hashTable[table[i][j]];
こうすれば、hash_sum[i][j]は、座標(0,0)から(i,j)までの偶奇情報になる。すると任意の範囲での偶奇情報は、XORすることで簡単に求められるようになる。
次にyでループする。あるy範囲(d,u)に対して考えたとき、まずy方向のHash値(偶奇情報)をマージしてしまう。
for (var x = 0; x < m; x++) hash_line[x] = hash_sum[u][x] ^ (d > 0 ? hash_sum[d - 1][x] : 0);
そうしたら、「あるHash値(偶奇情報)になる最後のx位置」を保管する。2^10パターンしかないので、辞書よりも配列を使うと早い。
var dict = new int[1 << 10 + 1]; for (int i = 0; i < 1 << 10 + 1; i++) dict[i] = -1; for (var x = 0; x < m; x++) dict[hash_line[x]] = x;
最後にxの左位置でループする(l)。各lに対し「すべての文字が偶数個になる」または「1文字だけ奇数になる」パターンを列挙し、それぞれの最後のx位置rをdict[]から探し出す。見つかったらこの四角形(u, l, d, r)で回文を作ることができる。
for (var l = 0; l < m; l++) { // すべて偶数個 candHashes[0] = l > 0 ? hash_line[l - 1] : 0; // 1種類だけ奇数個 for (int i = 0; i <= 9; i++) candHashes[i + 1] = hashTable[i] ^ (l > 0 ? hash_line[l - 1] : 0); foreach (var candHash in candHashes) { r = dict[candHash]; if (r == -1) continue; var newArea = (u - d + 1) * (r - l + 1); if (newArea <= maxArea) continue; // もっと大きい面積の解がすでにある maxArea = newArea; ret = new Tuple<int, int, int, int, int>(maxArea, d, l, u, r); } }
もう一つの条件「回文は前ゼロを含んではならない」は、四角形の面積に対し、ゼロが多すぎるか否かで判断できる。範囲内のゼロの数は、ゼロの累積和を作っておけば簡単に求めることができる。
以下は全コード。
public static Tuple<int, int, int, int, int> GetAns(int n, int m, List<List<int>> table) { // nで二重ループするので、n>mのときは反転させる。Flip()の実装は省略 var flipped = false; if (n > m) { table = Flip(table); var tmp = n; n = m; m = tmp; flipped = true; } var hashTable = Enumerable.Range(0, 10).Select(v => 1 << v).ToArray(); var zero_num_sum = GetZeroNumSum(table); var hash_sum = GetHashSum(table, hashTable); // 返り値。面積, r1, c1, r2, c2 var ret = new Tuple<int, int, int, int, int>(1, 0, 0, 0, 0); var maxArea = 1; var candHashes = new int[11]; var zero_sum_line = new int[m]; var hash_line = new int[m]; int r; for (int d = 0; d < n; d++) { for (int u = d; u < n; u++) { for (var x = 0; x < m; x++) zero_sum_line[x] = zero_num_sum[u][x] - (d > 0 ? zero_num_sum[d - 1][x] : 0); for (var x = 0; x < m; x++) hash_line[x] = hash_sum[u][x] ^ (d > 0 ? hash_sum[d - 1][x] : 0); // あるHash値(偶奇情報)になる最後のx位置 var dict = new int[1 << 10 + 1]; for (int i = 0; i < 1 << 10 + 1; i++) dict[i] = -1; for (var x = 0; x < m; x++) dict[hash_line[x]] = x; for (var l = 0; l < m; l++) { // すべて偶数個 candHashes[0] = l > 0 ? hash_line[l - 1] : 0; // 1種類だけ奇数個 for (int i = 0; i <= 9; i++) candHashes[i + 1] = hashTable[i] ^ (l > 0 ? hash_line[l - 1] : 0); foreach (var candHash in candHashes) { r = dict[candHash]; if (r == -1) continue; var newArea = (u - d + 1) * (r - l + 1); if (newArea <= maxArea) continue; // もっと大きい面積の解がすでにある // ゼロの数を判定 var zeroNum = zero_sum_line[r] - (l > 0 ? zero_sum_line[l - 1] : 0); if (newArea - zeroNum < 2) continue; maxArea = newArea; ret = new Tuple<int, int, int, int, int>(maxArea, d, l, u, r); } } } } if (flipped) Flip(ret); return ret; } public static List<List<int>> GetZeroNumSum(List<List<int>> table) { var ret = new List<List<int>>(); for (int i = 0; i < table.Count; i++) { var list = Enumerable.Range(0, table[0].Count).Select(v => 0).ToList(); ret.Add(list); } for (int i = 0; i < table.Count; i++) { for (int j = 0; j < table[0].Count; j++) { if (i > 0) ret[i][j] = ret[i - 1][j]; if (j > 0) ret[i][j] += ret[i][j - 1]; if (i > 0 && j > 0) ret[i][j] -= ret[i - 1][j - 1]; ret[i][j] += table[i][j] == 0 ? 1 : 0; } } return ret; } public static List<List<int>> GetHashSum(List<List<int>> table, int[] hashTable) { var ret = new List<List<int>>(); for (int i = 0; i < table.Count; i++) { var list = Enumerable.Range(0, table[0].Count).Select(v => 0).ToList(); ret.Add(list); } for (int i = 0; i < table.Count; i++) { for (int j = 0; j < table[0].Count; j++) { if (i > 0) ret[i][j] = ret[i - 1][j]; if (j > 0) ret[i][j] ^= ret[i][j - 1]; if (i > 0 && j > 0) ret[i][j] ^= ret[i - 1][j - 1]; ret[i][j] ^= hashTable[table[i][j]]; } } return ret; }
この問題が解けたのはうれしかった。あとは難易度Expertに手が出るようになれば…。
ノード数nの無向グラフが与えられる。ノードu,v,wについて、u-wパスとv-wパスが存在するか答えよ。ただし、2つのパスは同じノードを共有できない。
1 <= n <= 10^5
1 <= m(エッジ数) <= min( n*(n-1)/2, 2*10^5 )
1 <= q(クエリ数) <= 10^5
ノードをVertex Biconnected Componentsに分解して、Block-Cut Treeを作ることで解くことができる。Block-Cut TreeはVertex Biconnected ComponentsおよびArticulation(元ノードのうち橋になっているもの)をノードにして作成された木。
基本的にはBlock-Cut Tree上でu-w-vのパス有無を判定するだけだが、wがArticulationの場合は追加判定が必要になる。
if (lca_uw == lca_vw) { if (bicc.Articulations.Contains(orgW) && (bicc.Tree.DP[0][w] == lca_uv || bicc.Tree.DP[0][lca_uv] == w)) return "YES"; } if (lca_vw == lca_uv) { if (bicc.Articulations.Contains(orgW) && (bicc.Tree.DP[0][w] == lca_uw || bicc.Tree.DP[0][lca_uw] == w)) return "YES"; } if (lca_uv == lca_uw) { if (bicc.Articulations.Contains(orgW) && (bicc.Tree.DP[0][w] == lca_vw || bicc.Tree.DP[0][lca_vw] == w)) return "YES"; }
このように、u-LCA(u,v)-vを考えたとき、wのすぐ隣がこのLCA(u,v)ならOKになる。
また、与えられる木は連結とは限らないので、その判定も追加で必要。
public static List<string> GetAns(int n, int m, List<int[]> edges, List<int[]> queries) { var bicc = new BICC_BlockCut(n); foreach (var edge in edges) bicc.AddEdge(edge[0] - 1, edge[1] - 1); bicc.Calc(); bicc.BuildTree(); return queries.Select(q => GetAns(n, bicc, q)).ToList(); } public static string GetAns(int n, BICC_BlockCut bicc, int[] query) { var u = query[0] - 1; var v = query[1] - 1; var w = query[2] - 1; var orgU = u; var orgV = v; var orgW = w; // same start pos but w is different if (u == v && u != w) return "NO"; // already goaled if (u == w && v == w) return "YES"; // same component if (bicc.InSameComp(u, v) && bicc.InSameComp(v, w) && bicc.InSameComp(u, w)) return "YES"; u = bicc.NodeToComponentId[u]; v = bicc.NodeToComponentId[v]; w = bicc.NodeToComponentId[w]; // should be in the same tree if (!bicc.Tree.InSameTree(u, v) || !bicc.Tree.InSameTree(v, w) || !bicc.Tree.InSameTree(u, w)) return "NO"; if (bicc.Tree.Level[v] < bicc.Tree.Level[u]) { var tmp = u; u = v; v = tmp; } var lca_vw = bicc.Tree.Lca(v, w); var lca_uv = bicc.Tree.Lca(u, v); var lca_uw = bicc.Tree.Lca(u, w); if (lca_uw == lca_vw) { if (bicc.Articulations.Contains(orgW) && (bicc.Tree.DP[0][w] == lca_uv || bicc.Tree.DP[0][lca_uv] == w)) return "YES"; } if (lca_vw == lca_uv) { if (bicc.Articulations.Contains(orgW) && (bicc.Tree.DP[0][w] == lca_uw || bicc.Tree.DP[0][lca_uw] == w)) return "YES"; } if (lca_uv == lca_uw) { if (bicc.Articulations.Contains(orgW) && (bicc.Tree.DP[0][w] == lca_vw || bicc.Tree.DP[0][lca_vw] == w)) return "YES"; } return bicc.Tree.HasShortPath(u, v, w) ? "YES" : "NO"; } public class BICC_BlockCut { List<List<int>> _graph; int _n; // results public HashSet<int> Articulations; public List<List<Tuple<int, int>>> ComponentEdges; public List<HashSet<int>> ComponentNodes; public List<int> NodeToComponentId; // tree of BICC public Tree Tree; public List<int> SizeOfComponents; public BICC_BlockCut(int n) { _n = n; _graph = Enumerable.Range(0, n).Select(v => new List<int>()).ToList(); } public void AddEdge(int s, int t) { _graph[s].Add(t); _graph[t].Add(s); } List<SortedSet<int>> _nodeToComponentIds; int T; int[] ord; int[] low; int[] depth; Stack<Tuple<int, int>> stack; public void Calc() { ComponentEdges = new List<List<Tuple<int, int>>>(); low = Enumerable.Range(0, _n).Select(v => -1).ToArray(); depth = Enumerable.Range(0, _n).Select(v => -1).ToArray(); ord = Enumerable.Range(0, _n).Select(v => -1).ToArray(); stack = new Stack<Tuple<int, int>>(); for (int i = 0; i < _n; i++) { if (ord[i] == -1) { T = 0; Dfs(i); } } _nodeToComponentIds = Enumerable.Range(0, _n).Select(v => new SortedSet<int>()).ToList(); ComponentNodes = new List<HashSet<int>>(); for (int cId = 0; cId < ComponentEdges.Count(); cId++) { var edges = ComponentEdges[cId]; var hash = new HashSet<int>(); foreach (var e in edges) { hash.Add(e.Item1); hash.Add(e.Item2); _nodeToComponentIds[e.Item1].Add(cId); _nodeToComponentIds[e.Item2].Add(cId); } ComponentNodes.Add(hash); } } void Dfs(int i) { low[i] = ord[i] = T++; for (int j = 0; j < _graph[i].Count(); j++) { var to = _graph[i][j]; if (ord[to] == -1) { depth[to] = depth[i] + 1; stack.Push(new Tuple<int, int>(i, to)); Dfs(to); low[i] = Math.Min(low[i], low[to]); if (ord[i] == 0 || low[to] >= ord[i]) { var edges = new List<Tuple<int, int>>(); while (true) { if (IsEq(stack.Peek(), i, to)) break; edges.Add(stack.Pop()); } edges.Add(stack.Pop()); ComponentEdges.Add(edges); } } else if(depth[to] < depth[i] - 1) { low[i] = Math.Min(low[i], ord[to]); stack.Push(new Tuple<int, int>(i, to)); } } } bool IsEq(Tuple<int, int> edge, int u, int v) { return (edge.Item1 == u && edge.Item2 == v) || (edge.Item1 == v && edge.Item2 == u); } // need to run Calc() first public void BuildTree() { var tuple = GetTreeGraph(); Tree = new Tree(ComponentNodes.Count() + _n, tuple.Item1.Select(v => v.ToList()).ToList(), tuple.Item2); } Tuple<List<HashSet<int>>, HashSet<int>> GetTreeGraph() { Articulations = new HashSet<int>(); var C = ComponentNodes.Count(); SizeOfComponents = Enumerable.Range(0, C + _n).Select(v => 0).ToList(); var graph = Enumerable.Range(0, C + _n).Select(v => new HashSet<int>()).ToList(); NodeToComponentId = Enumerable.Range(0, C + _n).Select(v => -1).ToList(); var extra = Enumerable.Range(0, C + _n).Select(v => -1).ToList(); for (int i = 0; i < _nodeToComponentIds.Count(); i++) { var tmpv = _nodeToComponentIds[i]; if (_graph[i].Count() == 0) // isolated { NodeToComponentId[i] = C + i; extra[C + i] = 0; SizeOfComponents[C + i] = 1; } else if (tmpv.Count() == 1) // in single comp { NodeToComponentId[i] = tmpv.First(); extra[tmpv.First()] = 0; SizeOfComponents[tmpv.First()]++; } else // articulation vertex { Articulations.Add(i); NodeToComponentId[i] = C + i; extra[C + i] = 0; SizeOfComponents[C + i]++; foreach (var j in tmpv) { extra[j] = 0; SizeOfComponents[j]++; graph[C + i].Add(j); graph[j].Add(C + i); } } } var extraHash = new HashSet<int>(); for (int i = 0; i < extra.Count(); i++) { if (extra[i] == -1) extraHash.Add(i); } return new Tuple<List<HashSet<int>>, HashSet<int>>(graph, extraHash); } public bool InSameComp(int u, int v) { return _nodeToComponentIds[u].Intersect(_nodeToComponentIds[v]).Count() > 0; } } // basic tree with Lca() and Asc() public class Tree { public int[] Level; int LOGN = 20; public int[][] DP; int _n; List<List<int>> _graph; HashSet<int> _ignoreNodes; public Tree(int n) { Init(n); _graph = Enumerable.Range(0, n).Select(v => new List<int>()).ToList(); _ignoreNodes = new HashSet<int>(); } public Tree(int n, List<List<int>> graph, HashSet<int> ignoreNodes) { Init(n); _graph = graph; _ignoreNodes = ignoreNodes; Calc(); } void Init(int n) { _n = n; Level = new int[n]; DP = Enumerable.Range(0, LOGN).Select(v => Enumerable.Range(0, n).Select(w => 0).ToArray()).ToArray(); } public void AddEdge(int s, int t) { _graph[s].Add(t); _graph[t].Add(s); } // working for calc() int[] arr; int currComp; int[] dep; int[] vis; int T; public void Calc() { arr = new int[_n]; currComp = 0; dep = new int[_n]; vis = new int[_n]; T = 0; for (int i = 0; i < _n; i++) { if (vis[i] == 0 && !_ignoreNodes.Contains(i)) { currComp++; Dfs(i, i); } } for (int i = 1; i < LOGN; i++) { for (int j = 0; j < _n; j++) { if (!_ignoreNodes.Contains(j)) { DP[i][j] = DP[i - 1][DP[i - 1][j]]; } } } } void Dfs(int u, int p) { if (vis[u] != 0) return; Level[u] = Level[p] + 1; DP[0][u] = p; arr[u] = ++T; vis[u] = currComp; foreach (var w in _graph[u]) if (w != p) Dfs(w, u); dep[u] = T++; } public bool InSameTree(int a, int b) { return vis[a] == vis[b]; } public int Lca(int a, int b) { if (Level[a] > Level[b]) { var tmp = a; a = b; b = tmp; } int d = Level[b] - Level[a]; for (int i = 0; i < LOGN; i++) { if (((1 << i) & d) != 0) b = DP[i][b]; } if (a == b) return a; for (int i = LOGN - 1; i >= 0; i--) { if (DP[i][a] != DP[i][b]) { a = DP[i][a]; b = DP[i][b]; } } return DP[0][a]; } // p is Anc of u public bool Anc(int p, int u) { return (arr[u] >= arr[p] && dep[u] <= dep[p]); } // has short path: u - w - v public bool HasShortPath(int u, int v, int w) { if (!InSameTree(u, v) || !InSameTree(v, w)) return false; if (Level[u] > Level[v]) { var tmp = v; v = u; u = tmp; } int LCA = Lca(u, v); bool ok = false; ok |= Anc(w, u); ok |= Anc(w, v); ok &= Anc(LCA, w); return ok; } }
ずいぶんと解けそうで解けず苦しんだ。。。