UMD CP Club Summer CP
.Rising Sophomore
.ICPC Mid-Atlantic 3rd Place
.UMD CP Club President
.Codeforces Master
.AtCoder Rating \(\ge\) 2000
Feel free to discuss problems with me!
The problems are usually algorithmic
(CMSC351/451)
You have to come up with an algorithm
and implement It!
There are also two important parts
(Time Limit Exceeded)
(Memory Limit Exceeded)
(Wrong Answer)
(Accepted)
It varies from \(10^8 \sim 5 \times 10^8\)
We need a tool to approximate how many operations our code does!
We denote the runtime of an algorithm with
where \(n\) is the input size
\(f(n)\) is the number of operations with respect to \(n\)
The formal definition of Big O is
if and only if
The formal definition of Big O is
if and only if
We ignore the constant factor
The other viewpoint of Big O is
if and only if
for (int i = 1; i <= n; i++) {
sum += i;
}
What is the time complexity of this code?
for (int i = 1; i <= 2*n; i++) {
sum += i;
}
What is the time complexity of this code?
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
sum += i * j;
}
}
What is the time complexity of this code?
for (int i = 2; i <= n; i++) {
for(int j = 2 * i; j <= n; j += i){
isprime[j] = false;
}
}
What is the time complexity of this code?
for (int i = 2; i <= n; i++) {
for(int j = 2 * i; j <= n; j += i){
isprime[j] = false;
}
}
What is the time complexity of this code?
int f(int x){
if (x <= 1) {
return 1;
} else {
return f(x-1) + f(x-2);
}
}
What is the time complexity of this code?
You will learn different ways to approximate time complexity of recursion in CMSC351
Recursion Tree
Substitution Method
Master's Theorem
Knowing Master's Theorem will be sufficient
#include <bits/stdc++.h>
using namespace std;
int main(){
int a, b;
cin >> a >> b;
cout << a+b << "\n";
}
#include <bits/stdc++.h>
This header includes all the common headers
cin >>
cout <<
The above input things from standard input
The bottom output things to standard output
for (int i = 0; i < n; i++) {
//do something n times
}
while(condition) {
//do something when condition is true
}
Loops
The range of int
is approxiamtely
\([-2 \times 10^9, 2 \times 10^9]\)
The range of long long
is
\([-9 \times 10^{18}, 9 \times 10^{18}]\) |
cin, cout
and scanf(), printf()
cin, cout
ios_base::sync_with_stdio(0); cin.tie(0);
cin, cout is now 6x faster than scanf, printf !
Use "\n" instead of endl
int arr[n];
long long arr[n];
char arr[n];
string arr[n];
An array is initialized with T arr[length]
where T
is some type and length
is some integer
The size cannot be changed
int arr[n] = {1, 2, 3};
cout << arr[0] << "\n"; // 1
cout << arr[1] << "\n"; // 2
arr[1] = 0;
cout << arr[1] << "\n"; // 0
You can access elements with arr[i]
it is 0-based, the first element is arr[0]
There are two types of dynamic arrays
vector<T>
deque<T>
vector supports pushing back but not front
deque supports both pushing back and front
vector<T>
vector<int> v;
v.push_back(1); // v = {1}
cout << v[0] << "\n"; // 1
v.push_back(2); // v = {1, 2}
v.pop_back(); // v = {1}
v.push_back(3); // v = {1, 3}
cout << v[1] << "\n"; // 3
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
vector<T>
vector<int> v(len, x);
//initialize a vector of length len and initial element x
v[i]; //access the ith element
v.push_back(x); //add x to back
v.pop_back(); //remove last element
v.size(); //returns size of the vector
v.empty(); //returns true or false
v.resize(len); //resize the vector to len
v.clear(); //clear the vector
string
string in C++ is technically just vector<char>
but it also has
string s = "Hello";
s += "World";
cin >> s;
cout << s;
deque<T>
deque<int> dq;
dq.push_back(1); // v = {1}
cout << dq[0] << "\n"; // 1
dq.push_back(2); // v = {1, 2}
dq.pop_front(); // v = {2}
dq.push_back(3); // v = {2, 3}
cout << dq[0] << "\n"; // 2
for (int i = 0; i < dq.size(); i++) {
cout << dq[i] << " ";
}
deque<T>
deque<int> dq(len, x);
//initialize a deque of length len and initial element x
dq[i]; //access the ith element
dq.push_back(x); dq.push_front(x);//add x to back/front
dq.pop_back(); dq.pop_front(); //remove last/first element
dq.size(); //returns size of the deque
dq.empty(); //returns true or false
dq.resize(len); //resize the deque to len
dq.clear(); //clear the deque
It has a larger constant!
In C++, there are builtin functions that are fast and easy to use!
int arr[n] = {5, 4, 3, 2, 1};
vector<int> v = {3, 4, 5, 1, 2};
sort(arr, arr+n);
sort(v.begin(), v.end());
std::sort
has a time complexity of \(O(n \log n)\)
lower_bound(v.begin(), v.end(), x);
//return the iterator to first >= x
upper_bound(v.begin(), v.end(), x);
//return the iterator to first > x
unique(v.begin(), v.end());
//if v has k unique elements, set v[0], v[1], v[k-1] to those elements
Both lower and upper bound takes \(O(\log n)\)
unique takes \(O(n)\)
swap(&a, &b); //swap the value of a and b
fill(v.begin(), v.end(), x) //make all elements in range x
reverse(v.begin(), v.end()); //reverse the range
max_element(v.begin(), v.end()); //return iterator to maximum element
min_element(v.begin(), v.end()); //return iterator to minimum element
shuffle(v.begin(), v.end()); //random shuffle all elements in range
swap is \(O(1)\)
others are \(O(n)\)
Consider this simple task
You are given \(n\) points on 2d plane,
sort them first by \(x\) then by \(y\)
pair<T, T> p;
p.first; //returns first element
p.second; //returns second element
pair<T,T> make_pair(T a, T b); //return a pair {a,b}
{a, b}; //same as above
vector<pair<int,int>> v;
for (int i = 0; i < n; i++) {
v.push_back({x[i], y[i]});
}
sort(v.begin(), v.end());
It is a Last in First Out Data Structure
stack<int> st;
st.push(x); //push back
st.pop(); //pop back
st.top(); //returns last element
st.empty(); //return true or false
st.size(); //return size of stack
This is monotonic stack, we will focus on it more later
It is a First in First Out Data Structure
queue<int> q;
q.push(x); //push front
q.pop(); //pop front
q.front(); //returns first element
q.empty(); //return true or false
q.size(); //return size of queue
This data structure is used for BFS
We will use it more when we talk about it
You can simply used it as a sorted queue
priority_queue<int> pq;
pq.push(x); //push x into the queue
pq.pop(); //pop the largest element
pq.top(); //returns the largest element
pq.empty(); //return true or false
pq.size(); //return size of queue
push and pop are \(O(\log n)\)
You can also create min heap (default is max heap)
priority_queue<int, vector<int>, greater<>> pq;
pq.push(x); //push x into the queue
pq.pop(); //pop the smallest element
pq.top(); //returns the smallest element
pq.empty(); //return true or false
pq.size(); //return size of queue
This data structure is used the most in Greedy algorithms
There are two builtin binary search trees
set<T>
map<K,V>
Both are ordered
set<int> st;
st.insert(x); //add x to set
st.erase(x); //remove x from set
st.count(x); //most used as finding x
st.find(x); //it is harder to use than count
st.empty(); //return true or false
st.size(); //return size
st.lower_bound(x); //return iterator to first >= x
st.upper_bound(x); //return iterator to first > x
multiset<int> st;
st.insert(x); //add x to set, O(log n)
st.erase(x); //remove all x from set, O(log n)
st.count(x); //count the number of x, O(log n)
st.find(x); //find the first x, O(log n)
st.empty(); //return true or false, O(1)
st.size(); //return size, O(1)
st.lower_bound(x); //return iterator to first >= x, O(log n)
st.upper_bound(x); //return iterator to first > x, O(log n)
If you only want to remove an element
Use st.erase(st.find(x));
You can use it as an array for any data type
map<string, int> mp;
mp["hello"] = 1; //O(log n)
mp.insert("a", 2); // O(log n)
cout << mp["a"] << "\n"; // 2
mp.erase("hello"); // O(log n)
//other things are the same as set
mp.erase(Key); //remove Key from map O(log n)
mp.count(Key); //check if Key exists in map O(log n)
mp.find(Key); // O(log n)
mp.lower_bound(key); // O(log n)
mp.upper_bound(key); // O(log n)
Same as map, but doesn't support set operations
Everything becomes \(O(1)\) (expected)
These are just the basics, but it is important to know them
We will start discussing actual algorithms in the following weeks