March 20, 2019 Single Round Match 753 Editorials
Div II Easy: KerimJavati
For each l like L, it takes 2 * distance(‘a’, L) + 1 seconds for Kerim to type it.
Time complexity is O(text.size())
public static int howLong(String text) {
int ans = 0;
for (char c : text.toCharArray())
ans += 1 + 2 * (c - 'a');
return ans;
}
Div II Medium: CCChecker2
Simulate the process, several things to check:
- All elements in arrays int[] moveStartRow, int[] moveStartCol, int[] moveDestRow, int[] moveDestCol, must be between 1 and n.
- Never two cubes could take the same position.
- Cubes must arrive at their destination at the end.
- Moves must be valid.
Time complexity is O(moves * m), could be improved to O(moves + m + n ^ 2).
public static String check(int n, int[] startRow, int[] startCol, int[] destRow, int[] destCol, int[] moveStartRow, int[] moveStartCol, int[] moveDestRow, int[] moveDestCol){
int m = startCol.length, moves = moveStartCol.length;
if (checkArray(n, moveDestRow) || checkArray(n, moveStartRow) || checkArray(n, moveDestCol) || checkArray(n, moveStartCol))
return "invalid";
for(int i = 0; i < moves; i++) {
int id = -1;
for(int j = 0; j < m; j++)
if(startRow[j] == moveStartRow[i] && startCol[j] == moveStartCol[i])
id = j;
for(int j = 0; j < m; j++)
if(startRow[j] == moveDestRow[i] && startCol[j] == moveDestCol[i])
return "invalid";
if(abs(moveStartCol[i] - moveDestCol[i]) + abs(moveStartRow[i] - moveDestRow[i]) != 1)
return "invalid";
if(id == -1)
return "invalid";
startCol[id] = moveDestCol[i];
startRow[id] = moveDestRow[i];
}
for(int j = 0; j < m; j++)
if(startRow[j] != destRow[j] || startCol[j] != destCol[j])
return "invalid";
return "valid";
}
private static boolean checkArray(int n, int[] destRow) {
for(int x : destRow)
if(x < 1 || x > n)
return true;
return false;
}
Div II Hard: MaxCutFree
Consider bridges, they form a forest graph, solve the problem for each tree of this forest independently. Now the problem is converted to find the maximum independent set in a tree (a set that no two vertices out of that are adjacent). Use Dynamic Programming. For each vertex, keep two values:
- Close: Answer for the subtree of this vertex.
- Open: Answer for the subtree of this vertex if you don’t choose this vertex.
Now, dp[root].close is the answer.
public static class Edge{
int v, u;
public Edge(int v, int u) {
this.v = v;
this.u = u;
}
}
public static class Dp{
int open, close;
public Dp(int open, int close) {
this.open = open;
this.close = close;
}
}
public static final int maxn = (int) 3e5 + 17;
static int edge[], comp[], h[], gp, d[], cnt[];
static boolean bl[];
static boolean seen[];
static ArrayList<ArrayList<Integer> > g = new ArrayList<>();
static Edge e[] = new Edge[maxn];
static int hi(int v, int p){
int ret = h[v];
seen[v] = true;
int parEdge = 0;
for(int i : g.get(v)){
int u = edge[i] ^ v;
if(!seen[u]){
h[u] = h[v] + 1;
int t = hi(u, v);
if(t == h[u])
bl[i] = true;
ret = min(ret, t);
}
else if(u != p && u != v)
ret = min(ret, h[u]);
else if (u == p)
parEdge++;
}
if(parEdge > 1)
ret = min(ret, h[v] - 1);
return ret;
}
static void hello(int v){
cnt[ comp[v] = gp ]++;
for(int i : g.get(v)){
int u = edge[i] ^ v;
if(!bl[i] && comp[u] == -1)
hello(u);
}
}
static void build_tree(int m){
for(int i = 0; i < m; i++)
if(bl[i]) {
g.get(e[i].v).add(e[i].u);
g.get(e[i].u).add(e[i].v);
}
}
static Dp dfs(int v, int p){
seen[v] = true;
Dp ans = new Dp(0, 1);
for(int u : g.get(v))
if(u != p) {
Dp child = dfs(u, v);
ans.open += child.close;
ans.close += child.open;
}
ans.close = max(ans.close, ans.open);
return ans;
}
static public int solve(int n, int a[], int b[]){
int m = a.length;
edge = new int[maxn];
comp = new int[maxn];
h = new int[maxn];
d = new int[maxn];
cnt = new int[maxn];
bl = new boolean[maxn];
seen = new boolean[maxn];
comp = new int[n];
g = new ArrayList<>();
for(int i = 0; i < n; i++)
g.add(new ArrayList<>());
Arrays.fill(comp, -1);
for(int i = 0; i < m; i++) {
int v = a[i];
int u = b[i];
//System.err.println(v + " " + u);
e[i] = new Edge(v, u);
edge[i] = v ^ u;
g.get(v).add(i);
g.get(u).add(i);
}
for(int i = 0; i < n; i++)
if(!seen[i])
hi(i, -1);
for(int i = 0; i < n; i++)
if(comp[i] == -1){
hello(i);
cnt[gp] = cnt[gp] - 1;
gp++;
}
g = new ArrayList<>(n);
for(int i = 0; i < n; i++)
g.add(new ArrayList<>());
build_tree(m);
Arrays.fill(seen, false);
int ans = 0;
for(int i = 0; i < n; i++)
if(!seen[i])
ans += dfs(i, -1).close;
return ans;
MojisBag:
Keep trie of numbers at each moment and two elements that maximize the answer, e1, and, e2.
- If number x added to the bag, at first try to update the answer and if updated, update e1 and e2. Then add x to trie. Time complexity: O(log MAXV).
- If number x become removed from the bag, remove it from trie. If it’s e1 or e2, clear trie, insert elements one by one again and update the answer. Time complexity: O(q * log MAXV) if x is e1 or e2, O(log MAXV) otherwise.
What do you think about time complexity? The point is because queries are generated randomly, the probability of that x equals to e1 or e2 is 2 / n. So the mathematical expectation of time complexity is: q * (2 / q * q * log MAXV + (q – 2) / q * log MAXV) = q log (q).
static class Node {
int cnt, index;
Node left;
Node right;
public Node() {
cnt = 0;
left = null;
right = null;
index = -1;
}
}
static class Pair {
int key, value;
public Pair(int key, int value) {
this.key = key;
this.value = value;
}
}
static Node root = new Node();
static void insert(int value, int index) {
Node cur = root;
cur.cnt++;
for (int i = 31; i >= 0; i--) {
if (((1 << i) & value) == 0) {
if (cur.left == null) {
cur.left = new Node();
}
cur = cur.left;
}
else {
if (cur.right == null) {
cur.right = new Node();
}
cur = cur.right;
}
cur.cnt++;
}
cur.index = index;
}
static void remove(int value) {
Node cur = root;
root.cnt--;
for (int i = 31; i >= 0; i--) {
if (((1 << i) & value) == 0) {
cur = cur.left;
}
else {
cur = cur.right;
}
cur.cnt--;
}
}
static Pair getMax(int value) {
Node cur = root;
if(root.cnt == 0)
return new Pair(-1, 0);
int res = 0;
for (int i = 31; i >= 0; i--) {
int add = 0;
if (((1 << i) & value) == 0) {
if (cur.right != null && cur.right.cnt != 0) {
cur = cur.right;
add++;
}
else {
cur = cur.left;
}
}
else {
if (cur.left != null && cur.left.cnt != 0) {
cur = cur.left;
add++;
}
else {
cur = cur.right;
}
}
res = 2 * res + add;
}
return new Pair(cur.index, res);
}
public static int maximumXor(int q, int base, int add, int rate) {
final int Mod = (int) 1e9 + 7;
int ans = 0;
int a[] = new int[q];
int y[] = new int[q];
int z[] = new int[q];
boolean deleted[] = new boolean[q];
int cur = 0, sz = 0, cera = -1, cerb = -1;
for(int i = 0; i < q; i++){
cur = (int) (((long) cur * base + add) % Mod);
if(cur % rate != 0){
//System.out.println("+ " + cur);
Pair tmp = getMax(cur);
if(ans < tmp.value){
ans = tmp.value;
cera = sz;
cerb = tmp.key;
}
insert(cur, sz);
a[sz++] = cur;
}
else if(sz > 0){
int remIndex = cur % sz;
//System.out.println("- " + remIndex + " (" + a[remIndex] + ")");
if(!deleted[remIndex]) {
deleted[remIndex] = true;
remove(a[remIndex]);
if (remIndex == cera || remIndex == cerb) {
root = new Node();
ans = 0;
cera = cerb = -1;
for (int j = 0; j < sz; j++)
if(!deleted[j]){
Pair tmp = getMax(a[j]);
if (ans < tmp.value) {
ans = tmp.value;
cera = j;
cerb = tmp.key;
}
insert(a[j], j);
}
}
}
}
y[i] = ans;
//System.out.println("= " + ans);
}
z[0] = y[0];
for(int i = 1; i < q; i++)
z[i] = (int) (((long) z[i - 1] * base + y[i]) % Mod);
return z[q - 1];
}
MojiDeletes:
Consider xor of range [L, R] be X, we are searching for some i (L <= i <= R), such that maximizes the value X ^ a[i];
We know how to find value maximizing xor in a trie (like problem 706D from codeforces). Let’s keep a trie for each prefix of the array, name the trie constructed from numbers in the range [0, i], t[i]. For each node, keep count of the number of nodes in its subtree. Now, for answering query [L, R], we can subtract values written on nodes in t[L] from t[R + 1], from this trie we can find the answer.
One problem remains. Time complexity is now O(n ^ 2 * log MAXV). The key idea is to use persistent trie instead of constructing n tries, it reduces the time complexity to O(n * log MAXV).
private final static int Mod = (int) 1e9 + 7;
static class PersistentTrie
{
int node;
int left[],right[],data[];
int maxN=20000001;
PersistentTrie()
{
node=0;//0 is dummy node
left=new int[maxN];
right=new int[maxN];
data=new int[maxN];
}
/**Finds the number in this trie which maximizes xor with val.
* @param n is the root number of the version which is being queried.
* @param val is a 32 bit positive integer.
* @return A value from trie which maximizes the xor with val.
*/
int queryMax(int n, int m, int val)
{
int ans=0;
int y=1<<30;
while(y>0)
{
if((val&y)>0)//current bit is 1
{
if(data[left[m]] - data[left[n]]!=0)
{
ans+=y;
n = left[n];
m = left[m];
}
else if(data[right[m]] - data[right[n]]!=0) {
n = right[n];
m = right[m];
}
else
return ans;
}
else//current bit is 0
{
if(data[right[m]] - data[right[n]]!=0)
{
n=right[n];
m = right[m];
ans+=y;
}
else if(data[left[m]] - data[left[n]]!=0)
{
m = left[m];
n=left[n];
}
else
return ans;
}
y>>=1;
}
return ans;
}
/**
* Insert the value val in the trie version with root n.
* @param n Root number of trie version in which insertion is to be done.
* @param val Value to be inserted.
* @param y pow(2,30) in this case.
* @return
*/
int insert(int n,int val,int y)//returns the root of new trie
{
//create a new node
int cur=++node;
if(y==0)
{
data[cur]=1+data[n];
return cur;
}
if((val&y)>0)//current bit is 1
{
left[cur]=left[n];
data[cur]=data[left[n]];
int r=insert(right[n],val,y>>1);
right[cur]=r;
data[cur]+=data[r];
}
else
{
right[cur]=right[n];
data[cur]=data[right[n]];
int l=insert(left[n],val,y>>1);
left[cur]=l;
data[cur]+=data[l];
}
return cur;
}
}
public static long maximumXor(int n, int q, int base, int add, int qBase, int qAdd){
PersistentTrie pt = new PersistentTrie();
int a[] = new int[n + 1];
int ps[] = new int[n + 1];
int root[] = new int[n + 1];
root[0] = ps[0] = a[0] = 0;
for(int i = 1; i <= n; i++) {
a[i] = (int) (((long) a[i - 1] * base + add) % Mod);
root[i] = pt.insert(root[i - 1], a[i], 1 << 30);
ps[i] = ps[i - 1] ^ a[i];
//System.out.print(String.valueOf(a[i]) + ' ');
}
//System.out.println();
int cur = 0;
long ans = 0;
while(q-- > 0){
cur = (int) (((long) cur * qBase + qAdd) % n);
int l = cur;
cur = (int) (((long) cur * qBase + qAdd) % n);
int r = cur;
if(r < l) {
int t = l;
l = r;
r = t;
}
ans += pt.queryMax(root[l], root[r + 1], ps[r + 1] ^ ps[l]);
//System.out.println(String.valueOf(l + 1) + ", " + String.valueOf(r + 1) + " = " + pt.queryMax(root[l], root[r + 1], ps[r + 1] ^ ps[l]));
}
return ans;
}
Harshit Mehta
Sr. Community Evangelist