Problem statement
You are given a complete, balanced M-Ary Tree and must support Q queries. There are 3 kinds of queries. Return true or false depending on whether the query was successful.
Lock(v, id) - Lock vertex v for user - id
Unlock(v, id) - If vertex v is locked by the same id, unlock it.
Upgrade(v, id) - If v is unlocked and has at least one locked vertex in it's subtree and every locked vertex in the subtree of v is locked by id, unlock them and lock v instead.
Further, here are some additional constraints
A vertex cannot be locked if it has any locked ancestors or descendants, by any ID.
When a vertex is upgraded, it's locked descendants are automatically unlocked.
An upgrade operation is not possible if the vertex is already locked or has any locked ancestors
An unlock operation is only possible if the vertex is already locked and locked by the same id
P.S. - The question was very badly stated and not a single constraint was mentioned openly on the problem statement. A lot of these constraints had to be deduced by supplying custom input and observing the expected output. There were no constraints on the size of the variables.
Solution
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{ // TreeNode is used to store all information (i.e. parent, locked, locked by which id, children, locked descendents)
string name; // name of the node
int lockedBy; // store locked by which id
bool isLocked; // is node locked or not
vector<TreeNode*> childs; // store all childeren or current node
TreeNode* parent; // parent of current node
set<TreeNode*> lockedDescendents; // store node pointer of locked descendents
TreeNode(string nm, TreeNode* par){ //constructor to create node by passing node's name and partner node pointer. if
//current node is root node of tree than parent of root will be NULL
name=nm;
lockedBy=-1; //Initially we are assuming current node is not locked by any id (-1 means not locked by any id)
parent=par;
isLocked=false; // node is unlocked
}
void addChild(vector<string> &a){// create all the childs of current node
for(string &nm: a){
childs.push_back(new TreeNode(nm, this));
}
}
};
struct MAryTree{
TreeNode * root;// root of the tree
unordered_map<string, TreeNode*> nameToTreeNodeMapping; // store node name and node pointer mapping
MAryTree(string name){ // constructor of M-Ary-Tree
root= new TreeNode(name, nullptr);
}
void makeMArtTree(vector<string> &a, int m){// this method is used to build the tree (Initially).
queue<TreeNode*>q; // using queue to iterate all node like bfs approach
int k=1,i,n=a.size();
q.push(root); // push root node initially
while(!q.empty()){ // iterate node (pop node) and push all its childs in the queue.
TreeNode* r= q.front();
q.pop();
nameToTreeNodeMapping[r->name]=r;
vector<string>b;
for(i=k;i<min(n,k+m);i++){
b.push_back(a[i]);
}
r->addChild(b);
for(TreeNode* child: r->childs){ // insert all its childs ( of recently poped node) in the queue
q.push(child);
}
k=i;
}
}
void print(TreeNode *r){// method is used to print tree (for debug perpose).
if(!r)return;
cout<<"TreeNode -> "<<r->name<<" "<<r->lockedBy<<" "<<"\n";
cout<<"Childs -> \n";
for(TreeNode* child: r->childs){
cout<<" "<<child->name<<"\n";
}
cout<<"Locked -> \n";
for(TreeNode* child: r->lockedDescendents){
cout<<" "<<child->name<<"\n";
}
for(TreeNode* child: r->childs){
print(child);
}
}
void updateParents(TreeNode * r, TreeNode * curr){ // inform all ancestor about its child locking in O(logN base M)
// N is the total no of node in the tree
// M - > M-ary-tree
while(r){
r->lockedDescendents.insert(curr);
r=r->parent;
}
}
bool lock(string name, int id){ // method is required to lock the node which is given in problem statement
TreeNode* r= nameToTreeNodeMapping[name];// fetch the node pointer using its name
if(r->isLocked || r->lockedDescendents.size())return false; // check if node is already locked and have any locked ancestor than return false (if any condition become true)
TreeNode *par= r->parent;
while(par){// check if any ancestor is locked or not (by any id) if yes than we can't lock current node
if(par->isLocked)return false;
par=par->parent;
}
updateParents(r->parent, r);// inform all ancestor about its locking in O(logm(n) time)
r->isLocked=true;// lock the node
r->lockedBy=id;
return true;
}
bool unlock(string name, int id){
TreeNode* r= nameToTreeNodeMapping[name];
if(!r->isLocked || r->lockedBy!=id)return false; // return false if node is unlocked or locked by different id
TreeNode *par= r->parent;
while(par){// inform all Ancestors about its unlocking
par->lockedDescendents.erase(r);
par=par->parent;
}
r->isLocked=false;// unlock it
r->lockedBy=-1;
return true;
}
bool upgradeLock(string name, int id){
TreeNode* r= nameToTreeNodeMapping[name];
if(r->isLocked || r->lockedDescendents.size()==0)return false;// return false if node is already locked or have 0 locked descendants
for(TreeNode* ld: r->lockedDescendents){// check all the descendants are locked by same id or not
if(ld->lockedBy!=id)return false;
}
TreeNode *par= r->parent;
while(par){// check if any ancestor is locked or not
if(par->isLocked)return false;
par=par->parent;
}
set<TreeNode*>stt= r->lockedDescendents;
for(TreeNode* ld: stt){// unlock all descendants
// cout<<ld->name<<"\n";
unlock(ld->name, id);
}
lock(name,id);// lock current node
return true;
}
};
int main() {
int n,m,t,i,opType,id;
string name;
cin>>n>>m>>t;
vector<string>a(n);
for(i=0;i<n;i++){
cin>>a[i];
}
MAryTree * tree= new MAryTree(a[0]);
tree->makeMArtTree(a, m);
//tree->print(tree->root);
while(t--){
cin>>opType>>name>>id;
if(opType==1){
if(tree->lock(name, id)){
cout<<"true";
}
else cout<<"false";
} else if (opType==2) {
if(tree->unlock(name, id)){
cout<<"true";
}
else cout<<"false";
} else if (opType==3) {
if(tree->upgradeLock(name, id)){
cout<<"true";
}
else cout<<"false";
}
cout<<"\n";
}
}Time complexity ->
(1) lock operation -> O(logm(n)) -> log n base m
logic -> we will check all descendents are locked or not (in O(1) means constant time) and ancestors are locked or not by exploring each ancestor one by one (in logm(n)) time
(2) unlock -> O(logm(n)) -> log n base m
logic -> if given node is locked and locked by same id then we will inform all anesctors about its unlocking in logm(n) by exploring
each ancestor one by one.
(3) upgrade -> O(no of locked descendantslogm(n))
logic -> check one or more than one descendants are locked by same Id or not (if 5 descendants are locked then we can check in O(5) time)
-- check 0 ancestors are locked or not in O(logm(n)) time
--- unlock all descendants in O(no of locked descendantslogm(n)) time
Space complexity
O(n)
Hackathon part B solution coming very soon.
Please upvote it.
Thanks!!