/* ACM ICPC -- CERC 2011 * Solution for the problem: Regulate * Idea: keep a balanced tree for each path in each company's network * - update the tree by a split (selling a cable) or merge (buying) * - balance by rebuilding (amortized sqrt(N) per operation) * Author: Josef Cibulka */ import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.StringTokenizer; import java.util.Arrays; import java.util.ArrayList; import java.util.Comparator; import java.util.Collections; import java.util.List; public class regulate { final static int MAXN=10000; final static int MAXM=100000; final static int MAXC=100; final static int MAXT=100000; public static class Trees { int parent[]; int l[]; int r[]; int new_tree_list[]; boolean reverse[]; final static int MAXDEP=100; // depth of a query after which a tree is rebuilt public Trees(int n) { parent = new int[n]; l = new int[n]; r = new int[n]; reverse = new boolean[n]; new_tree_list = new int[n]; for(int i=0; iend) return -1; int mid = (beg+end)/2; int nmid = new_tree_list[mid]; parent[nmid] = _parent; l[nmid] = build_opt_tree(nmid, beg, mid-1); r[nmid] = build_opt_tree(nmid, mid+1, end); reverse[nmid] = false; return nmid; } // if root is reverted, "sends" reverse to the two children void push_reverse_down(int root) { if(!reverse[root]) return; int lr = l[root], rr = r[root]; if(lr!=-1) reverse[lr] = !reverse[lr]; if(rr!=-1) reverse[rr] = !reverse[rr]; l[root] = rr; r[root] = lr; reverse[root] = false; } // if v is reverted, "sends" reverse to the parent and the sibling void push_reverse_up(int v) { if(!reverse[v]) return; int par = parent[v]; if(par!=-1) { reverse[par] = !reverse[par]; int lp = l[par], rp = r[par]; if(lp!=-1) reverse[lp] = !reverse[lp]; if(rp!=-1) reverse[rp] = !reverse[rp]; l[par] = rp; r[par] = lp; } else reverse[v] = false; // we can change the direction of a path } int ntl_size; static int fn_stack[] = new int[MAXN]; static int fn_stack_size; static boolean fn_stack_processed[] = new boolean[MAXN]; // dfs with infix writing of nodes into new_tree_list void find_nodes(int root) { // dfs without recursion as java can recurse to depth about 1000 if(root==-1) return; fn_stack_size = 1; fn_stack[0] = root; fn_stack_processed[0] = false; while(fn_stack_size > 0) { fn_stack_size--; int cur = fn_stack[fn_stack_size]; if(fn_stack_processed[fn_stack_size]) new_tree_list[ntl_size++] = cur; else { push_reverse_down(cur); if(r[cur]!=-1) { fn_stack_processed[fn_stack_size] = false; fn_stack[fn_stack_size++] = r[cur]; } fn_stack_processed[fn_stack_size] = true; fn_stack[fn_stack_size++] = cur; if(l[cur]!=-1) { fn_stack_processed[fn_stack_size] = false; fn_stack[fn_stack_size++] = l[cur]; } } } } // keep the root (because its id is reported as the path number) and // rebuild its subtrees private void rebuild(int root) { push_reverse_down(root); ntl_size = 0; find_nodes(l[root]); l[root] = build_opt_tree(root, 0, ntl_size-1); ntl_size = 0; find_nodes(r[root]); r[root] = build_opt_tree(root, 0, ntl_size-1); } public int query(int qi, boolean can_rebuild) { int pos = qi; int dst; push_reverse_up(pos); for(dst=0; parent[pos]!=-1; dst++) { pos = parent[pos]; push_reverse_up(pos); } if(can_rebuild && dst>=MAXDEP) rebuild(pos); return pos; } // merge the prepared trees rooted in root1 (left tree) and root2 (right tree) private void merge_prepared(int root1, int root2) { int lr = root1; // the rightmost in the left tree push_reverse_down(lr); while(r[lr]!=-1) { lr = r[lr]; push_reverse_down(lr); } // set left son of lr to the right son of parent[lr] if(lr!=root1) { int lrpar = parent[lr]; int lrson = l[lr]; if(lrson!=-1) parent[lrson] = lrpar; if(lrpar!=-1) r[lrpar] = lrson; } // make lr the new root parent[lr]=-1; if(lr!=root1) { l[lr] = root1; parent[root1] = lr; } r[lr] = root2; parent[root2] = lr; } // merge trees containing m1 and m2 (expects that the nodes are extremes of // the path represented by their trees and that the trees are different) public void merge(int m1, int m2) { int root1 = query(m1, true); // also clear all rever- int root2 = query(m2, true); // ses on the path mi<->ri int p1 = parent[m1]; int p2 = parent[m2]; boolean is1right = (p1==-1)?(r[m1]==-1):(r[p1]==m1); boolean is2left = (p2==-1)?(l[m2]==-1):(l[p2]==m2); if(is1right != is2left) { reverse[root2] = !reverse[root2]; is2left = !is2left; } if(is1right) merge_prepared(root1,root2); else merge_prepared(root2,root1); } // split between s1 and s2 public void split(int s1, int s2) { // s1 and s2 are neighbors on the path, so one is an ancestor of the other // find who is the ancestor and split on the edge from it towards the descendant int cur = s1, prev = -1; while(cur!=s2 && cur!=-1) { prev = cur; cur = parent[cur]; } if(cur==-1) { cur = s2; while(cur!=s1 && cur!=-1) { prev = cur; cur = parent[cur]; } } query(prev, false); // to clear all reverses // split on the tree-edge prev-cur int lroot=-1, rroot=-1; boolean just_started = true; while(cur!=-1) { if(prev == l[cur] && (rroot!=-1 || just_started)) { lroot = prev; parent[prev] = -1; l[cur] = rroot; if(rroot!=-1) parent[rroot] = cur; rroot = -1; } else if(prev == r[cur] && (lroot!=-1 || just_started)) { rroot = prev; parent[prev] = -1; r[cur] = lroot; if(lroot!=-1) parent[lroot] = cur; lroot = -1; } just_started = false; prev = cur; cur = parent[cur]; } } } StringTokenizer st = new StringTokenizer(""); BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); String nextToken() throws Exception { while (!st.hasMoreTokens()) st = new StringTokenizer(input.readLine()); return st.nextToken(); } int nextInt() throws Exception { int ret = Integer.parseInt(nextToken()); return ret; } public static void main(String[] args) throws Exception { regulate inst = new regulate(); for(int ii=1; ; ii++) { try { inst.run(ii); } catch(Exception e) { return; } } } public class Edge { int owner, to; Edge(int _to, int _owner) { to = _to; owner = _owner; } } public class EdgeComparator implements Comparator { public int compare(Edge e1, Edge e2) { return e1.to-e2.to; } } boolean run(int ii) throws Exception { int n = nextInt(); if (n<=0) throw new Exception(); int m = nextInt(); int c = nextInt(); int t = nextInt(); Trees tree[] = new Trees[c]; for (int i = 0; i < c; i++) tree[i] = new Trees(n); int deg[][] = new int[c][n]; // java initializes to 0's List ed[] = new List[n]; for (int i = 0; i < n; ++i) ed[i] = new ArrayList(2*c); Comparator edgeCompar = new EdgeComparator(); for(int i=0; is2) { int tmp = s1; s1 = s2; s2 = tmp; } ed[s1].add(new Edge(s2,o)); deg[o][s1]++; deg[o][s2]++; tree[o].merge(s1,s2); } for(int s1=0; s1s2) { int tmp = s1; s1 = s2; s2 = tmp; } int epos = Collections.binarySearch(ed[s1], new Edge(s2, 0), edgeCompar); if(epos<0) { System.out.println("No such cable."); continue; } int prevo = ed[s1].get(epos).owner; if(o == prevo) { System.out.println("Already owned."); continue; } if(deg[o][s1]==2 || deg[o][s2]==2) { System.out.println("Forbidden: monopoly."); continue; } int r1 = tree[o].query(s1, true); int r2 = tree[o].query(s2, true); if(r1==r2) { System.out.println("Forbidden: redundant."); continue; } tree[o].merge(s1,s2); deg[prevo][s1]--; deg[prevo][s2]--; tree[prevo].split(s1,s2); deg[o][s1]++; deg[o][s2]++; ed[s1].get(epos).owner = o; System.out.println("Sold."); } System.out.println(); return true; } }