17. Vectors in C++
1. Introduction
Vectors are similar to arrays. They can store large amounts of data of the same type.
However, unlike arrays, vectors can grow
and shrink in size during program execution. As you can expect based on your knowledge of arrays, this re-sizing does not come for free. We'll discuss this later. However, the idea is that the size of a vector does not need to be
known in advance.
Here is an example of a problem where a vector is a convenient tool.
Problem 1.
The user enters integers one by one. Input ends when the user enters -9.
After entering -9, the user provides one more positive integer, q.
The program must add q to each of the previously entered integers and print
the updated integers in the same order they were received.
You may notice that this is the same problem as Problem 1 from
the chapter on linked lists. We solved that problem using a linked list. In this chapter,
we will solve it using a vector.
Before we can solve this problem, we need to learn how to use vectors in C++. Vectors are
part of the C++ standard library. To use them, we must include the appropriate header file.
2. Declaring and initializing
A vector is declared by specifying the type of data it will store. The declaration of a
vector of long integers is done as follows:
The above declaration creates an empty vector. The computer is asked to give us a vector
that can store values of type long, and we agree to call it v.
It is also possible to declare a vector with a specific number of elements. The following
declaration creates a vector of 10 elements of type long:
The elements of the vector are indexed starting from 0. The first element is
v[0], the second is v[1], and the last is v[9].
This is the same indexing convention as for arrays.
If we want all elements to be initialized to a specific value, say 0, we can
write:
std::vector<long> v(10, 0);
More generally, the declaration std::vector<long> v(n, x); creates a
vector of n elements, each initialized to the value x.
Finally, a vector can be initialized from a list of values:
std::vector<long> v = {3, 1, 4, 1, 5, 9};
This creates a vector of 6 elements with the given values. The compiler flag
-std=c++11 is required for this syntax, just as it was required for
nullptr in the chapter on linked lists.
3. Accessing elements
The elements of a vector can be accessed using the [] operator, in the same
way as arrays. The following code declares a vector of 5 elements, sets the first element
to 10, and prints it.
std::vector<long> v(5);
v[0] = 10;
std::cout << v[0] << std::endl;
The [] operator does not check whether the index is within the valid range.
Accessing an element outside the valid range using [] results in undefined
behavior, in the same way that accessing an invalid memory address using a pointer can
cause a program to crash or behave unexpectedly.
The .at() method provides an alternative way to access elements. Unlike
[], the method .at() checks whether the index is within the
valid range. If the index is out of range, the program will terminate with an error
message rather than behaving unpredictably.
std::vector<long> v(5);
v.at(0) = 10;
std::cout << v.at(0) << std::endl;
For a beginner, .at() is the safer choice. However, [] is
faster, since it skips the range check. We use []
when we are confident that the index is within range, and .at() when we
are not.
4. Size, and adding and removing elements
The number of elements currently stored in a vector is called its size. The size
of a vector can be obtained using the .size() method.
std::vector<long> v = {3, 1, 4, 1, 5, 9};
std::cout << v.size() << std::endl;
The above code will print 6, since the vector has 6 elements.
A new element can be added to the end of a vector using the .push_back()
method. The following code creates an empty vector and adds three elements to it.
std::vector<long> v;
v.push_back(3);
v.push_back(1);
v.push_back(4);
std::cout << v.size() << std::endl;
After the three calls to .push_back(), the vector contains the elements
3, 1, and 4, in that order. The call to
.size() will print 3.
The last element of a vector can be removed using the .pop_back() method.
std::vector<long> v = {3, 1, 4, 1, 5, 9};
v.pop_back();
std::cout << v.size() << std::endl;
After the call to .pop_back(), the vector contains 5 elements:
3, 1, 4, 1, and 5.
The element 9 has been removed.
We can now solve the problem from the introduction. The solution reads integers from
the user using .push_back(), and then prints the updated values.
// vectors.cpp
// compile with
// c++ vectors.cpp -o vectors -std=c++11
// execute with
// ./vectors
#include <iostream>
#include <vector>
int main(){
std::vector<long> v;
long userInput;
std::cin >> userInput;
while(userInput != -9){
v.push_back(userInput);
std::cin >> userInput;
}
long q;
std::cin >> q;
for(long i = 0; i < v.size(); ++i){
std::cout << v[i] + q << " ";
}
std::cout << std::endl;
return 0;
}
Notice that unlike the linked list solution, there is no need to allocate or free memory
manually. The vector takes care of this on our behalf.
5. Iterating over a vector
In the solution above, we used an index-based loop to iterate over the vector. This is
the most straightforward way to iterate over a vector, and it will be the most common
approach in this course.
std::vector<long> v = {3, 1, 4, 1, 5, 9};
for(long i = 0; i < v.size(); ++i){
std::cout << v[i] << " ";
}
C++ also provides a more compact syntax for iterating over a vector, called a
range-based for loop. The following code prints the same output as the code above.
std::vector<long> v = {3, 1, 4, 1, 5, 9};
for(long x : v){
std::cout << x << " ";
}
The range-based for loop declares a variable x that takes the value of each
element of the vector in order. The loop body is executed once for each element. Note that
x is a copy of the element, not the element itself. Modifying x
inside the loop does not modify the vector.
Remark: You may encounter code that iterates over a vector using an
iterator, with syntax such as v.begin() and v.end().
Iterators are a more general concept that applies to all standard library containers,
including the sets and maps from earlier chapters. We will not use iterators directly in
this course, but it is useful to recognize the syntax when reading code written by others.
6. Vectors and memory
A vector is not magic. Internally, a vector stores its elements in a block of memory
that is allocated on the heap, in the same way that we have been allocating memory
manually using new. When we call .push_back() and the vector
needs more space, it requests a new, larger block of memory from the computer, copies
all existing elements to the new block, and releases the old block. This is the
re-sizing cost that we mentioned in the introduction.
The vector keeps track of two numbers: its size, which is the number of elements
currently stored, and its capacity, which is the size of the memory block that
has been allocated. The capacity is always at least as large as the size.
When the capacity is exceeded, the vector increases its capacity by some amount. The exact policy depends on the implementation. In 2026, when these notes were last updated, the most common compilers gcc and clang would double the capacity when .push_back() is called on a vector whose size equals its capacity. Since the capacity grows geometrically, most calls to .push_back() are fast, but occasionally one call will trigger a re-allocation. The current capacity of a vector can be obtained using
the .capacity() method.
std::vector<long> v;
for(long i = 0; i < 10; ++i){
v.push_back(i);
std::cout << "size=" << v.size();
std::cout << " capacity=" << v.capacity() << "\n";
}
We encourage the reader to compile and run the above code and observe how the capacity
grows in steps while the size grows by one at each iteration.
When a vector goes out of scope, it automatically releases the memory it has allocated.
The programmer has no way to release it manually, nor any reason to. This is the main practical advantage of
vectors over manual memory management: the programmer does not need to remember to free
the memory, and memory leaks of the kind we discussed in the chapter on pointers cannot
occur.
7. Comparing vectors
Vectors support the comparison operator <. Two vectors are compared
lexicographically: the first elements are compared first, and if they are equal, the
second elements are compared, and so on. If all elements of the shorter vector are equal
to the corresponding elements of the longer vector, then the shorter vector is considered
smaller.
std::vector<long> a = {1, 2, 3};
std::vector<long> b = {1, 2, 4};
if(a < b){
std::cout << "a is smaller\n";
}
The above code will print a is smaller, since the first two elements of
a and b are equal, and the third element of a
is smaller than the third element of b.
This means that vectors can be used as keys in a std::map and ssm::map or stored in a
std::set and ssm::set, since both require the < operator to be defined
on their elements. This turns out to be useful in dynamic programming, where we
sometimes need to map a vector of parameters to a previously computed result.
8. Practice problems
Problem 2.
In this problem, we will implement a building block for a primitive database where each record consists of a key, which is a vector of integers of fixed dimension, and a value, which is a string. The distinguishing feature of this database is that it supports efficient ranking of records by any chosen coordinate of the key. This first problem asks you to implement a struct that will make this possible.
Create appropriate structs and methods that solve the problem presented below. You must use the keyword const whenever possible.
Implement a struct FlaggedVector that wraps a std::vector<long> and contains a distinguished coordinate called flag. The struct should support the following:
- (a)
operator[] (both const and non-const versions) that provides access to the elements of the wrapped vector.
- (b)
size that returns the size of the wrapped vector.
- (c)
resize that resizes the wrapped vector.
- (d)
operator< that compares two FlaggedVector objects. The comparison should sort primarily by the flag-th coordinate, with full lexicographic order as a tiebreaker. If flag is out of range, fall back to lexicographic order directly.
A FlaggedVector is a vector of integers with one distinguished coordinate, called the flag. Its purpose is to allow the same vector data to be sorted in different ways depending on which coordinate is flagged: two FlaggedVector objects are compared primarily by their flag-th coordinate, with full lexicographic order as a tiebreaker. If flag is out of range, the comparison falls back to lexicographic order directly. The remaining methods — operator[], size, and resize — simply delegate to the wrapped std::vector.
#include<iostream>
#include<vector>
struct FlaggedVector{
public:
std::vector<long> v;
long flag;
long& operator[](long );
const long& operator[](long ) const;
long size() const;
void resize(long );
bool operator<(const FlaggedVector& ) const;
};
long& FlaggedVector::operator[](long i){
return v[i];
}
const long& FlaggedVector::operator[](long i) const{
return v[i];
}
long FlaggedVector::size() const{
return v.size();
}
void FlaggedVector::resize(long n){
v.resize(n);
}
bool FlaggedVector::operator<(const FlaggedVector& b) const{
if( (flag<0) || (flag>=v.size()) || (flag>=b.v.size()) ){
return (v<b.v);
}
if (v[flag]<b.v[flag]){return true;}
if (v[flag]>b.v[flag]){return false;}
return (v<b.v);
}
int main(){
long length;
FlaggedVector x;
FlaggedVector y;
std::cout<<"What is the length of the flagged vectors? ";
std::cin>>length;
if(length<1){
length=2;
std::cout<<"Overriding the illegal length. The new length is "<<length<<"\n";
}
x.resize(length); y.resize(length);
std::cout<<"Which coordinate will be the flag? ";
std::cin>>x.flag;
if((x.flag<0)||(x.flag>=length)){
x.flag=0;
std::cout<<"Overriding the illegal flag. The new flag is "<<x.flag<<"\n";
}
y.flag=x.flag;
std::cout<<"Insert the components of x: ";
for(long i=0;i<length;++i){
std::cin>>x[i];
}
std::cout<<"Insert the components of y: ";
for(long i=0;i<length;++i){
std::cin>>y[i];
}
if(x<y){
std::cout<<"x is smaller than y.\n";
}
else if(y<x){
std::cout<<"y is smaller than x.\n";
}
else{
std::cout<<"x and y are equal.\n";
}
std::cout<<"Now changing the flag of both vectors to demonstrate ";
std::cout<<"that the same vectors can be ordered differently.\n";
long newFlag;
std::cout<<"Insert a new flag: ";
std::cin>>newFlag;
if((newFlag<0)||(newFlag>=length)){
newFlag=0;
std::cout<<"Overriding the illegal flag. The new flag is "<<newFlag<<"\n";
}
x.flag=newFlag; y.flag=newFlag;
if(x<y){
std::cout<<"With the new flag, x is smaller than y.\n";
}
else if(y<x){
std::cout<<"With the new flag, y is smaller than x.\n";
}
else{
std::cout<<"With the new flag, x and y are equal.\n";
}
return 0;
}
Problem 3.
This problem continues the development of the primitive database introduced in Problem 2.
Create appropriate structs and methods that solve the problem presented below. You must use the keyword const whenever possible.
Implement a struct Record that represents a single entry in the database. It should contain:
- (a) A key of type
std::vector<long>.
- (b) A value of type
std::string.
- (c)
operator< that compares two Record objects lexicographically by key.
A Record is a simple struct that pairs a key, which is a vector of integers, with a value, which is a string. Records are ordered lexicographically by key, which is achieved by delegating operator< directly to std::vector's built-in lexicographic comparison.
#include<iostream>
#include<vector>
#include<string>
struct Record{
public:
std::vector<long> key;
std::string text;
bool operator<(const Record& ) const;
};
bool Record::operator<(const Record& b) const{
return (key<b.key);
}
int main(){
Record r1, r2, r3;
r1.key={1,2,3}; r1.text="first";
r2.key={1,2,4}; r2.text="second";
r3.key={1,2,3}; r3.text="third";
if(r1<r2){
std::cout<<"r1 is smaller than r2.\n";
}
if(r3<r1){
std::cout<<"r3 is smaller than r1.\n";
}
else if(r1<r3){
std::cout<<"r1 is smaller than r3.\n";
}
else{
std::cout<<"r1 and r3 are equal (as records).\n";
}
return 0;
}
Problem 4.
This problem continues the development of the primitive database introduced in Problems 2 and 3. We now implement a basic database without ranking support.
When a search operation fails to find the requested key, it signals failure by returning \(-1\) as the first component of the returned pair.
Create a class MultiRankDB with the following private attributes:
long dim;
ssm::set<Record> recordStorage;
Your class must contain the following methods:
- (a)
format with one argument m of type long. The method resets the database and sets the key dimension to m. If m is negative, the call is ignored. This operation is destructive — all existing records are erased regardless of the previous value of dim.
- (b)
dimension should return the current value of dim.
- (c)
size should return the number of records currently stored.
- (d)
insert with two arguments: key of type const std::vector<long>& and text of type const std::string&. The method inserts a record with the given key and text. If a record with that key already exists, its text is updated. If key.size() != dim, the call is ignored. The cost should be \(O(\log n)\), where \(n\) is the number of records currently stored.
- (e)
erase with one argument key of type const std::vector<long>&. The method removes the record with the given key, if it exists. The cost should be \(O(\log n)\).
- (f)
find with one argument key of type const std::vector<long>&. The method returns a std::pair<long, std::string> where the first component is the rank of the record in recordStorage and the second is its text. If the key is not found, the first component is \(-1\). The cost should be \(O(\log n)\).
We now build the first version of MultiRankDB, which supports basic database operations without ranking. The class maintains a single ssm::set<Record> called recordStorage, which stores all records sorted lexicographically by key. This gives \(O(\log n)\) cost for insertion, erasure, and search. Note that format is a destructive operation — it erases all existing records regardless of the previous value of dim. In insert, when a record with the given key already exists, its text is updated by erasing and re-inserting; this costs three traversals since ssm::set does not support in-place update.
#include<iostream>
#include<vector>
#include<string>
#include "ssm.cpp"
struct Record{
public:
std::vector<long> key;
std::string text;
bool operator<(const Record& ) const;
};
bool Record::operator<(const Record& b) const{
return (key<b.key);
}
class MultiRankDB{
private:
long dim=0;
ssm::set<Record> recordStorage;
public:
long dimension() const;
void format(long );
long size() const;
void insert(const std::vector<long>&, const std::string&);
void erase(const std::vector<long>& );
std::pair<long,std::string> find(const std::vector<long>& ) const;
};
long MultiRankDB::dimension() const{ return dim;}
void MultiRankDB::format(long m){
if(m<0){return;}
recordStorage.clear();
dim=m;
}
long MultiRankDB::size() const{
return recordStorage.size();
}
void MultiRankDB::insert(const std::vector<long>& key, const std::string& text){
if(key.size()!=dim){return;}
Record insRecord;
insRecord.key=key; insRecord.text=text;
if(recordStorage.find(insRecord)>-1){
// Key already exists: update text by erasing and re-inserting.
// This costs three traversals; ssm::set does not support in-place update.
recordStorage.erase(insRecord);
recordStorage.insert(insRecord);
return;
}
recordStorage.insert(insRecord);
}
void MultiRankDB::erase(const std::vector<long>& key){
Record eRecord;
eRecord.key=key;
recordStorage.erase(eRecord);
}
std::pair<long,std::string> MultiRankDB::find(const std::vector<long>& key) const{
std::pair<long,std::string> res;
res.first=-1;
if(key.size()!=dim){return res;}
Record searchRec;
searchRec.key=key;
std::pair<long,Record> iSPair=recordStorage.findIK(searchRec);
res.first=iSPair.first;
res.second=iSPair.second.text;
return res;
}
int main(){
MultiRankDB d;
d.format(3);
d.insert({1,2,3},"first");
d.insert({4,5,6},"second");
d.insert({1,2,3},"updated first");
std::cout<<"Size: "<<d.size()<<"\n";
std::pair<long,std::string> res=d.find({1,2,3});
if(res.first>-1){
std::cout<<"Found at rank "<<res.first<<": "<<res.second<<"\n";
}
res=d.find({7,8,9});
if(res.first==-1){
std::cout<<"Key {7,8,9} not found.\n";
}
d.erase({1,2,3});
std::cout<<"Size after erase: "<<d.size()<<"\n";
res=d.find({1,2,3});
if(res.first==-1){
std::cout<<"Key {1,2,3} not found after erase.\n";
}
return 0;
}
Problem 5.
This problem extends the class MultiRankDB from Problem 4 by adding support for ranking records by a single chosen coordinate. When a search operation fails to find the requested key, it signals failure by returning \(-1\) as the first component of the returned pair. When sortAndFind fails, it returns the vector <-1>, by which we mean a vector of length 1 whose single element is \(-1\). This convention is unambiguous because a valid key always has length dim.
Add the following private attribute to MultiRankDB:
std::vector<ssm::set<FlaggedVector>> keyRankings;
The attribute keyRankings is a vector of length dim, where the \(k\)-th component stores all keys sorted primarily by their \(k\)-th coordinate, using FlaggedVector with flag set to \(k\). Update format, insert, and erase accordingly to maintain keyRankings, and add the following methods:
- (a)
findRankK with two arguments: key of type const std::vector<long>& and k of type long. The method returns a std::pair<long, std::string> where the first component is the rank of the record with respect to coordinate \(k\), and the second is its text. If the key is not found or \(k\) is out of range, the first component is \(-1\). The cost should be \(O(\log n)\).
- (b)
sortAndFind with two arguments k and j of type long. The method returns a std::pair<std::vector<long>, std::string> where the first component is the key of the \(j\)-th record in the \(k\)-coordinate ordering, and the second is its text. If \(k\) is outside \([0, \mathtt{dimension}()-1]\) or \(j\) is outside \([0, \mathtt{size}()-1]\), the first component is <-1>.
We now extend MultiRankDB from Problem 4 by adding keyRankings, a vector of ssm::set<FlaggedVector> of length dim. The \(k\)-th set stores all keys sorted primarily by their \(k\)-th coordinate, using FlaggedVector with flag set to \(k\). This allows us to retrieve the rank of any record with respect to any single coordinate in \(O(\log n)\). The methods format, insert, and erase must be updated to maintain keyRankings alongside recordStorage. Note that in insert, when a record with the given key already exists, keyRankings does not need updating since the key vector is unchanged — only the text is updated in recordStorage.
#include<iostream>
#include<vector>
#include<string>
#include "ssm.cpp"
struct FlaggedVector{
public:
std::vector<long> v;
long flag;
long& operator[](long );
const long& operator[](long ) const;
long size() const;
void resize(long );
bool operator<(const FlaggedVector& ) const;
};
long& FlaggedVector::operator[](long i){
return v[i];
}
const long& FlaggedVector::operator[](long i) const{
return v[i];
}
long FlaggedVector::size() const{
return v.size();
}
void FlaggedVector::resize(long n){
v.resize(n);
}
bool FlaggedVector::operator<(const FlaggedVector& b) const{
if( (flag<0) || (flag>=v.size()) || (flag>=b.v.size()) ){
return (v<b.v);
}
if (v[flag]<b.v[flag]){return true;}
if (v[flag]>b.v[flag]){return false;}
return (v<b.v);
}
struct Record{
public:
std::vector<long> key;
std::string text;
bool operator<(const Record& ) const;
};
bool Record::operator<(const Record& b) const{
return (key<b.key);
}
class MultiRankDB{
private:
long dim=0;
ssm::set<Record> recordStorage;
std::vector<ssm::set<FlaggedVector>> keyRankings;
public:
long dimension() const;
void format(long );
long size() const;
void insert(const std::vector<long>&, const std::string&);
void erase(const std::vector<long>& );
std::pair<long,std::string> find(const std::vector<long>& ) const;
std::pair<long,std::string> findRankK(const std::vector<long>&, long) const;
std::pair<std::vector<long>,std::string> sortAndFind(long, long) const;
};
long MultiRankDB::dimension() const{ return dim;}
void MultiRankDB::format(long m){
if(m<0){return;}
keyRankings.clear();
recordStorage.clear();
dim=m;
keyRankings.resize(m);
}
long MultiRankDB::size() const{
return recordStorage.size();
}
void MultiRankDB::insert(const std::vector<long>& key, const std::string& text){
if(key.size()!=dim){return;}
Record insRecord;
insRecord.key=key; insRecord.text=text;
if(recordStorage.find(insRecord)>-1){
// Key already exists: update text by erasing and re-inserting.
// keyRankings does not need updating since the key vector is unchanged.
// This costs three traversals; ssm::set does not support in-place update.
recordStorage.erase(insRecord);
recordStorage.insert(insRecord);
return;
}
recordStorage.insert(insRecord);
FlaggedVector insV;
insV.v=key;
for(long k=0;k<dim;++k){
insV.flag=k;
keyRankings[k].insert(insV);
}
}
void MultiRankDB::erase(const std::vector<long>& key){
Record eRecord;
eRecord.key=key;
recordStorage.erase(eRecord);
FlaggedVector eV;
eV.v=key;
for(long k=0;k<dim;++k){
eV.flag=k;
keyRankings[k].erase(eV);
}
}
std::pair<long,std::string> MultiRankDB::find(const std::vector<long>& key) const{
std::pair<long,std::string> res;
res.first=-1;
if(key.size()!=dim){return res;}
Record searchRec;
searchRec.key=key;
std::pair<long,Record> iSPair=recordStorage.findIK(searchRec);
res.first=iSPair.first;
res.second=iSPair.second.text;
return res;
}
std::pair<long,std::string> MultiRankDB::findRankK(const std::vector<long>& key, long k) const{
std::pair<long,std::string> res;
res.first=-1;
if((k<0)||(k>=dim)){return res;}
if(key.size()!=dim){return res;}
FlaggedVector fV;
fV.v=key;
fV.flag=k;
res.first=keyRankings[k].find(fV);
if(res.first>-1){
Record searchRec;
searchRec.key=key;
std::pair<long,Record> iSPair=recordStorage.findIK(searchRec);
if(iSPair.first>-1){
res.second=iSPair.second.text;
}
}
return res;
}
std::pair<std::vector<long>,std::string> MultiRankDB::sortAndFind(long k, long j) const{
std::pair<std::vector<long>,std::string> res;
res.first.resize(1);
res.first[0]=-1;
if((k<0)||(k>=dim)){return res;}
if((j<0)||(j>=recordStorage.size())){return res;}
// Note: keyRankings[k][j] below is O(log n), not O(1) -- it is an indexed
// access into an AVL tree, not an array.
res.first=(keyRankings[k][j]).v;
Record searchRec;
searchRec.key=res.first;
std::pair<long,Record> iSPair=recordStorage.findIK(searchRec);
if(iSPair.first>-1){
res.second=iSPair.second.text;
}
return res;
}
int main(){
MultiRankDB d;
d.format(3);
d.insert({3,1,2},"first");
d.insert({1,3,2},"second");
d.insert({2,2,1},"third");
d.insert({1,1,3},"fourth");
std::cout<<"Size: "<<d.size()<<"\n";
std::vector<long> key={1,3,2};
long k=0;
std::pair<long,std::string> res=d.findRankK(key,k);
if(res.first>-1){
std::cout<<"Key {1,3,2} has rank "<<res.first<<" by coordinate "<<k<<": "<<res.second<<"\n";
}
k=1;
res=d.findRankK(key,k);
if(res.first>-1){
std::cout<<"Key {1,3,2} has rank "<<res.first<<" by coordinate "<<k<<": "<<res.second<<"\n";
}
std::cout<<"Sorted by coordinate 0:\n";
for(long j=0;j<d.size();++j){
std::pair<std::vector<long>,std::string> sRes=d.sortAndFind(0,j);
std::cout<<" rank "<<j<<": {"<<sRes.first[0]<<","<<sRes.first[1]<<","<<sRes.first[2]<<"} -> "<<sRes.second<<"\n";
}
std::cout<<"Sorted by coordinate 1:\n";
for(long j=0;j<d.size();++j){
std::pair<std::vector<long>,std::string> sRes=d.sortAndFind(1,j);
std::cout<<" rank "<<j<<": {"<<sRes.first[0]<<","<<sRes.first[1]<<","<<sRes.first[2]<<"} -> "<<sRes.second<<"\n";
}
return 0;
}
Problem 6.
This problem extends the class MultiRankDB from Problem 5 by adding support for retrieving the rankings of a record with respect to all coordinates simultaneously. When the key is not found, the method signals failure by returning the vector <-1>, by which we mean a vector of length 1 whose single element is \(-1\). This convention is unambiguous because a valid ranking vector always has length dim.
Add the following method to MultiRankDB:
- (a)
findAllRanks with one argument key of type const std::vector<long>&. The method returns a std::pair<std::vector<long>, std::string> where the first component is a vector of length dim containing the rank of the record with respect to each coordinate, and the second is its text. If the key is not found, the first component is <-1>. The cost should be \(O(\mathtt{dim} \cdot \log n)\).
We now extend MultiRankDB from Problem 5 by adding findAllRanks, which retrieves the rankings of a record with respect to all coordinates simultaneously. Compared to findRankK, which costs \(O(\log n)\) by searching only one set in keyRankings, findAllRanks costs \(O(\mathtt{dim} \cdot \log n)\) since it searches all dim sets. If the key is not found, the method returns the vector <-1>, a vector of length 1 whose single element is \(-1\), which is unambiguous since a valid ranking vector always has length dim.
#include<iostream>
#include<vector>
#include<string>
#include "ssm.cpp"
struct FlaggedVector{
public:
std::vector<long> v;
long flag;
long& operator[](long );
const long& operator[](long ) const;
long size() const;
void resize(long );
bool operator<(const FlaggedVector& ) const;
};
long& FlaggedVector::operator[](long i){
return v[i];
}
const long& FlaggedVector::operator[](long i) const{
return v[i];
}
long FlaggedVector::size() const{
return v.size();
}
void FlaggedVector::resize(long n){
v.resize(n);
}
bool FlaggedVector::operator<(const FlaggedVector& b) const{
if( (flag<0) || (flag>=v.size()) || (flag>=b.v.size()) ){
return (v<b.v);
}
if (v[flag]<b.v[flag]){return true;}
if (v[flag]>b.v[flag]){return false;}
return (v<b.v);
}
struct Record{
public:
std::vector<long> key;
std::string text;
bool operator<(const Record& ) const;
};
bool Record::operator<(const Record& b) const{
return (key<b.key);
}
class MultiRankDB{
private:
long dim=0;
ssm::set<Record> recordStorage;
std::vector<ssm::set<FlaggedVector>> keyRankings;
public:
long dimension() const;
void format(long );
long size() const;
void insert(const std::vector<long>&, const std::string&);
void erase(const std::vector<long>& );
std::pair<long,std::string> find(const std::vector<long>& ) const;
std::pair<long,std::string> findRankK(const std::vector<long>&, long) const;
std::pair<std::vector<long>,std::string> sortAndFind(long, long) const;
std::pair<std::vector<long>,std::string> findAllRanks(const std::vector<long>&) const;
};
long MultiRankDB::dimension() const{ return dim;}
void MultiRankDB::format(long m){
if(m<0){return;}
keyRankings.clear();
recordStorage.clear();
dim=m;
keyRankings.resize(m);
}
long MultiRankDB::size() const{
return recordStorage.size();
}
void MultiRankDB::insert(const std::vector<long>& key, const std::string& text){
if(key.size()!=dim){return;}
Record insRecord;
insRecord.key=key; insRecord.text=text;
if(recordStorage.find(insRecord)>-1){
// Key already exists: update text by erasing and re-inserting.
// keyRankings does not need updating since the key vector is unchanged.
// This costs three traversals; ssm::set does not support in-place update.
recordStorage.erase(insRecord);
recordStorage.insert(insRecord);
return;
}
recordStorage.insert(insRecord);
FlaggedVector insV;
insV.v=key;
for(long k=0;k<dim;++k){
insV.flag=k;
keyRankings[k].insert(insV);
}
}
void MultiRankDB::erase(const std::vector<long>& key){
Record eRecord;
eRecord.key=key;
recordStorage.erase(eRecord);
FlaggedVector eV;
eV.v=key;
for(long k=0;k<dim;++k){
eV.flag=k;
keyRankings[k].erase(eV);
}
}
std::pair<long,std::string> MultiRankDB::find(const std::vector<long>& key) const{
std::pair<long,std::string> res;
res.first=-1;
if(key.size()!=dim){return res;}
Record searchRec;
searchRec.key=key;
std::pair<long,Record> iSPair=recordStorage.findIK(searchRec);
res.first=iSPair.first;
res.second=iSPair.second.text;
return res;
}
std::pair<long,std::string> MultiRankDB::findRankK(const std::vector<long>& key, long k) const{
std::pair<long,std::string> res;
res.first=-1;
if((k<0)||(k>=dim)){return res;}
if(key.size()!=dim){return res;}
FlaggedVector fV;
fV.v=key;
fV.flag=k;
res.first=keyRankings[k].find(fV);
if(res.first>-1){
Record searchRec;
searchRec.key=key;
std::pair<long,Record> iSPair=recordStorage.findIK(searchRec);
if(iSPair.first>-1){
res.second=iSPair.second.text;
}
}
return res;
}
std::pair<std::vector<long>,std::string> MultiRankDB::sortAndFind(long k, long j) const{
std::pair<std::vector<long>,std::string> res;
res.first.resize(1);
res.first[0]=-1;
if((k<0)||(k>=dim)){return res;}
if((j<0)||(j>=recordStorage.size())){return res;}
// Note: keyRankings[k][j] below is O(log n), not O(1) -- it is an indexed
// access into an AVL tree, not an array.
res.first=(keyRankings[k][j]).v;
Record searchRec;
searchRec.key=res.first;
std::pair<long,Record> iSPair=recordStorage.findIK(searchRec);
if(iSPair.first>-1){
res.second=iSPair.second.text;
}
return res;
}
std::pair<std::vector<long>,std::string> MultiRankDB::findAllRanks(const std::vector<long>& key) const{
std::pair<std::vector<long>,std::string> res;
res.first.resize(1);
res.first[0]=-1;
if(dim!=key.size()){return res;}
Record searchRec;
searchRec.key=key;
long ind=recordStorage.find(searchRec);
if(ind<0){return res;}
res.second=recordStorage[ind].text;
res.first.resize(dim);
FlaggedVector fV;
fV.v=key;
for(long k=0;k<dim;++k){
fV.flag=k;
(res.first)[k]=keyRankings[k].find(fV);
}
return res;
}
int main(){
MultiRankDB d;
d.format(3);
d.insert({3,1,2},"first");
d.insert({1,3,2},"second");
d.insert({2,2,1},"third");
d.insert({1,1,3},"fourth");
std::vector<long> key={1,3,2};
std::pair<std::vector<long>,std::string> res=d.findAllRanks(key);
if((res.first.size()==1)&&(res.first[0]==-1)){
std::cout<<"Key not found.\n";
}
else{
std::cout<<"Key {1,3,2} found: "<<res.second<<"\n";
std::cout<<"Rankings: ";
for(long k=0;k<d.dimension();++k){
std::cout<<"coordinate "<<k<<": "<<res.first[k]<<" ";
}
std::cout<<"\n";
}
res=d.findAllRanks({7,8,9});
if((res.first.size()==1)&&(res.first[0]==-1)){
std::cout<<"Key {7,8,9} not found.\n";
}
return 0;
}
Problem 7.
This problem concludes the development of the primitive database built in Problems 2--6.
Write a main function that tests and exercises all aspects of the database, including formatting, inserting, erasing, and all search operations: find, findRankK, findAllRanks, and sortAndFind.
Rather than testing the database with a fixed sequence of operations, we build an interactive menu that allows the user to exercise all aspects of MultiRankDB freely. This approach is valuable beyond this specific problem: when developing a complex data structure, an interactive testing harness lets you probe edge cases, verify behavior after sequences of operations, and build intuition about the structure — all without recompiling. The menu is implemented as a simple string-comparison loop in main, where each command dispatches to the appropriate method of MultiRankDB.
#include<iostream>
#include<vector>
#include<string>
#include "ssm.cpp"
struct FlaggedVector{
public:
std::vector<long> v;
long flag;
long& operator[](long );
const long& operator[](long ) const;
long size() const;
void resize(long );
bool operator<(const FlaggedVector& ) const;
};
long& FlaggedVector::operator[](long i){
return v[i];
}
const long& FlaggedVector::operator[](long i) const{
return v[i];
}
long FlaggedVector::size() const{
return v.size();
}
void FlaggedVector::resize(long n){
v.resize(n);
}
bool FlaggedVector::operator<(const FlaggedVector& b) const{
if( (flag<0) || (flag>=v.size()) || (flag>=b.v.size()) ){
return (v<b.v);
}
if (v[flag]<b.v[flag]){return true;}
if (v[flag]>b.v[flag]){return false;}
return (v<b.v);
}
struct Record{
public:
std::vector<long> key;
std::string text;
bool operator<(const Record& ) const;
};
bool Record::operator<(const Record& b) const{
return (key<b.key);
}
class MultiRankDB{
private:
long dim=0;
ssm::set<Record> recordStorage;
std::vector<ssm::set<FlaggedVector>> keyRankings;
public:
long dimension() const;
void format(long );
long size() const;
void insert(const std::vector<long>&, const std::string&);
void erase(const std::vector<long>& );
std::pair<long,std::string> find(const std::vector<long>& ) const;
std::pair<long,std::string> findRankK(const std::vector<long>&, long) const;
std::pair<std::vector<long>,std::string> sortAndFind(long, long) const;
std::pair<std::vector<long>,std::string> findAllRanks(const std::vector<long>&) const;
};
long MultiRankDB::dimension() const{ return dim;}
void MultiRankDB::format(long m){
if(m<0){return;}
keyRankings.clear();
recordStorage.clear();
dim=m;
keyRankings.resize(m);
}
long MultiRankDB::size() const{
return recordStorage.size();
}
void MultiRankDB::insert(const std::vector<long>& key, const std::string& text){
if(key.size()!=dim){return;}
Record insRecord;
insRecord.key=key; insRecord.text=text;
if(recordStorage.find(insRecord)>-1){
// Key already exists: update text by erasing and re-inserting.
// keyRankings does not need updating since the key vector is unchanged.
// This costs three traversals; ssm::set does not support in-place update.
recordStorage.erase(insRecord);
recordStorage.insert(insRecord);
return;
}
recordStorage.insert(insRecord);
FlaggedVector insV;
insV.v=key;
for(long k=0;k<dim;++k){
insV.flag=k;
keyRankings[k].insert(insV);
}
}
void MultiRankDB::erase(const std::vector<long>& key){
Record eRecord;
eRecord.key=key;
recordStorage.erase(eRecord);
FlaggedVector eV;
eV.v=key;
for(long k=0;k<dim;++k){
eV.flag=k;
keyRankings[k].erase(eV);
}
}
std::pair<long,std::string> MultiRankDB::find(const std::vector<long>& key) const{
std::pair<long,std::string> res;
res.first=-1;
if(key.size()!=dim){return res;}
Record searchRec;
searchRec.key=key;
std::pair<long,Record> iSPair=recordStorage.findIK(searchRec);
res.first=iSPair.first;
res.second=iSPair.second.text;
return res;
}
std::pair<long,std::string> MultiRankDB::findRankK(const std::vector<long>& key, long k) const{
std::pair<long,std::string> res;
res.first=-1;
if((k<0)||(k>=dim)){return res;}
if(key.size()!=dim){return res;}
FlaggedVector fV;
fV.v=key;
fV.flag=k;
res.first=keyRankings[k].find(fV);
if(res.first>-1){
Record searchRec;
searchRec.key=key;
std::pair<long,Record> iSPair=recordStorage.findIK(searchRec);
if(iSPair.first>-1){
res.second=iSPair.second.text;
}
}
return res;
}
std::pair<std::vector<long>,std::string> MultiRankDB::sortAndFind(long k, long j) const{
std::pair<std::vector<long>,std::string> res;
res.first.resize(1);
res.first[0]=-1;
if((k<0)||(k>=dim)){return res;}
if((j<0)||(j>=recordStorage.size())){return res;}
// Note: keyRankings[k][j] below is O(log n), not O(1) -- it is an indexed
// access into an AVL tree, not an array.
res.first=(keyRankings[k][j]).v;
Record searchRec;
searchRec.key=res.first;
std::pair<long,Record> iSPair=recordStorage.findIK(searchRec);
if(iSPair.first>-1){
res.second=iSPair.second.text;
}
return res;
}
std::pair<std::vector<long>,std::string> MultiRankDB::findAllRanks(const std::vector<long>& key) const{
std::pair<std::vector<long>,std::string> res;
res.first.resize(1);
res.first[0]=-1;
if(dim!=key.size()){return res;}
Record searchRec;
searchRec.key=key;
long ind=recordStorage.find(searchRec);
if(ind<0){return res;}
res.second=recordStorage[ind].text;
res.first.resize(dim);
FlaggedVector fV;
fV.v=key;
for(long k=0;k<dim;++k){
fV.flag=k;
(res.first)[k]=keyRankings[k].find(fV);
}
return res;
}
std::string menu(){
std::string res;
res+="exit\t Exit\n";
res+="menu\t Menu\n";
res+="dimension\t Get vector length\n";
res+="size\t Print total size\n";
res+="format\t Format database (destructive)\n";
res+="insert\t Insert\n";
res+="erase\t Erase\n";
res+="find\t Find\n";
res+="findRankK\t Find rank by coordinate k\n";
res+="findAllRanks\t Find all ranks\n";
res+="sorted\t Sorted search\n";
res+="sortedPrint\t Sorted print\n";
return res;
}
std::vector<long> getVector(long m){
if(m<1){m=3;}
std::vector<long> res;
res.resize(m);
std::cout<<"Insert vector of size "<<m<<": ";
for(long i=0;i<m;++i){
std::cin>>res[i];
}
return res;
}
std::string toString(const std::vector<long>& v, const std::string& sep=","){
if(v.size()<1){return "";}
std::string res="<";
for(long i=0;i<v.size();++i){
if(res.size()>1){
res+=sep;
}
res+=std::to_string(v[i]);
}
res+=">";
return res;
}
int main(){
MultiRankDB d;
std::string uI;
std::cout<<menu()<<"\n";
std::cout<<": ";
std::cin>>uI;
while(uI!="exit"){
if(uI=="menu"){std::cout<<menu();}
if(uI=="dimension"){
std::cout<<d.dimension()<<"\n";
}
if(uI=="format"){
long n;
std::cout<<"What is the new length (this will erase database; negative number to cancel operation)? ";
std::cin>>n;
d.format(n);
}
if(uI=="size"){
std::cout<<d.size()<<"\n";
}
if(uI=="insert"){
if(d.dimension()==0){
d.format(3);
}
std::vector<long> uIM=getVector(d.dimension());
std::cout<<"Insert string: ";
std::string uIS;
std::cin>>uIS;
d.insert(uIM,uIS);
}
if(uI=="erase"){
if(d.dimension()==0){
d.format(3);
}
d.erase(getVector(d.dimension()));
}
if(uI=="find"){
std::pair<long,std::string> res=d.find(getVector(d.dimension()));
if(res.first==-1){
std::cout<<"Not found\n";
}
else{
std::cout<<"Rank="<<res.first<<"\nValue: "<<res.second<<"\n";
}
}
if(uI=="findAllRanks"){
std::pair<std::vector<long>,std::string> res=d.findAllRanks(getVector(d.dimension()));
if((res.first.size()==1)&&(res.first[0]==-1)){
std::cout<<"Not found\n";
}
else{
std::cout<<"Rankings="<<toString(res.first)<<"\nValue: "<<res.second<<"\n";
}
}
if(uI=="findRankK"){
std::vector<long> key=getVector(d.dimension());
long k;
std::cout<<"What is the index k for ranking? ";
std::cin>>k;
std::pair<long,std::string> res=d.findRankK(key,k);
if(res.first==-1){
std::cout<<"Not found\n";
}
else{
std::cout<<k<<"-ranking is "<<res.first<<"\nValue: "<<res.second<<"\n";
}
}
if(uI=="sorted"){
long k,j;
std::cout<<"Insert the index k for ranking and the rank j that you want to see: ";
std::cin>>k>>j;
std::pair<std::vector<long>,std::string> res=d.sortAndFind(k,j);
if((res.first.size()==1)&&(res.first[0]==-1)){
std::cout<<"Out of range. The number k (which is "<<k<<") ";
std::cout<<"must belong to the interval [0,"<<d.dimension()-1<<"] and the ";
std::cout<<"number j must belong to the interval [0,"<<d.size()-1<<"].\n";
}
else{
std::cout<<toString(res.first)<<"->"<<res.second<<"\n";
}
}
if(uI=="sortedPrint"){
long k;
std::cout<<"Insert the index k for ranking: ";
std::cin>>k;
std::pair<std::vector<long>,std::string> res;
for(long j=0;j<d.size();++j){
res=d.sortAndFind(k,j);
std::cout<<toString(res.first)<<"->"<<res.second<<"\n";
}
}
std::cout<<": ";
std::cin>>uI;
}
return 0;
}
Problem 8. The first two elements of user input are positive
integers m and n. After m and n are
received from the input, the user provides a positive integer k that is
strictly smaller than \(n\) and a vector called forbidden vector whose length is \(k\)
and whose all terms are from the set \(\{0\), \(1\), \(\dots\), \(m-1\}\). Create a recursive
algorithm (a dynamic programming algorithm will be asked in the next problem) that counts
all sequences of length \(n\) whose elements are from the set \(\{0\), \(1\), \(\dots\), \(m-1\}\)
and that do not contain the forbidden vector as a contiguous subarray.
We will design a recursive function that solves a more general problem. The recursive
function will be called countS and will have the following five parameters:
- 1. The vector
forbidden;
- 2. The vector
endV, a circular buffer of size \(k\) that stores
the last \(k-1\) elements of the sequence built so far;
- 3. The integer
endShift, which indicates the position in
endV where the next element will be written;
- 4. and 5. The integers \(n\) and \(m\).
The function counts the number of sequences of length \(n\) whose elements are from
\(\{0, 1, \dots, m-1\}\) and that do not complete the forbidden subarray, given that the
last \(k-1\) elements already placed are stored in endV.
We use the Boss-Employee analogy. The Boss is responsible for placing the last element
of the sequence, at index \(n-1\). Once the Boss places an element, the Boss hires an
Employee to count all valid sequences of length \(n-1\) that are consistent with the
updated ending window. The Employee faces the same problem as the Boss, but with a
shorter sequence, so the recursion is natural.
The key observation is that when deciding which value to place next, we do not need to
know the entire sequence built so far. We only need to know the last \(k-1\) elements,
since those are the only elements that could combine with the next element to form the
forbidden subarray. This is what endV stores.
A naive implementation would shift the contents of endV at each recursive
call, which would cost \(O(k)\) extra work per call. Instead, we treat endV
as a circular buffer. The variable endShift keeps track of which position
in endV corresponds to the most recently placed element. When the Boss
places a new element, it is written into position endShift. The Employee
then receives the updated endV with endShift decremented by
one (modulo \(k\)), so that the oldest element in the window is correctly overwritten at
the next recursive call. No copying or shifting of the vector is needed.
Before placing a new element, the Boss must determine whether any value in
\(\{0, 1, \dots, m-1\}\) is forbidden. This is the job of the helper function
startValueToAvoid. The function checks whether the last \(k-1\) elements
currently stored in endV match forbidden[1] through
forbidden[k-1]. If they all match, then placing forbidden[0]
next would complete the forbidden subarray, so the function returns
forbidden[0] as the value to avoid. If there is any mismatch, then no
element is dangerous, and the function returns -1.
The return value -1 is a deliberate choice. Since all valid elements are
in \(\{0, 1, \dots, m-1\}\), the value -1 will never equal i
in the loop while(i<m). This means that the condition
i!=vToAvoid is automatically true for all i when no value
is forbidden, without requiring any extra branching in the code.
The base case of the recursion is \(n=0\): an empty sequence does not contain any
subarray, so it is always valid and the function returns 1.
In main, the vector endV is initialized with \(k\) entries
all equal to -1. This represents an empty ending window at the start of
the recursion, before any elements have been placed. Since -1 is not in
\(\{0, 1, \dots, m-1\}\), these initial values will never match any entry of
forbidden, which correctly reflects the fact that no elements have been
placed yet.
#include<iostream>
#include<vector>
long startValueToAvoid(const std::vector<long>& forbidden,
const std::vector<long>& endV,
long endVLast){
long j=1;
while(j<forbidden.size()){
if(forbidden[j]!=endV[(j+endVLast+1)%forbidden.size()]){
return -1;
}
++j;
}
return forbidden[0];
}
long countS(const std::vector<long>& forbidden,
std::vector<long>& endV,
long endShift,
long n,
long m){
if(n<1){
return 1;
}
long total=0;
long i=0;
long valSave=endV[endShift];
long vToAvoid=startValueToAvoid(forbidden,
endV,
endShift+endV.size()-1);
long endShiftEmpl=(endShift+forbidden.size()-1)%forbidden.size();
while(i<m){
if(i!=vToAvoid){
endV[endShift]=i;
total+=countS(forbidden,endV,
endShiftEmpl,
n-1,
m);
}
++i;
}
endV[endShift]=valSave;
return total;
}
int main(){
long m,n,k;
std::cin>>m>>n>>k;
std::vector<long> forbidden(k);
std::vector<long> endV(k,-1);
for(long i=0;i<k;++i){
std::cin>>forbidden[i];
}
std::cout<<countS(forbidden,endV,k-1,n,m)<<"\n";
return 0;
}
Problem 9. Use memoization to improve the efficiency of the solution of problem 8.
The dynamic programming solution is obtained by adding memoization to the recursive
solution from the previous problem. We store previously computed results in an
ssm::set called knowledge. The ssm::set is the
balanced tree implementation from the chapter on sets and maps (simplified), which
supports indexed access and does not require iterators.
To use ssm::set as a memo table, we need a structure that represents the
state of a subproblem. We define the structure Memo with three fields:
- 1.
endV: the canonicalized ending window, representing the last
\(k-1\) elements of the sequence built so far;
- 2.
n: the number of elements still to be placed;
- 3.
numArrays: the number of valid sequences, i.e. the answer
to this subproblem.
The fields endV and n together uniquely identify a subproblem.
The field numArrays stores the result. Notice that operator<
compares only n and endV, and intentionally does not compare
numArrays. This is the correct behavior: two Memo objects
represent the same subproblem if and only if their n and endV
fields are equal, regardless of the value stored in numArrays. In this way,
the ssm::set is used to emulate a map: the key is the pair
(n, endV) and the value is numArrays.
The recursive function uses the circular buffer endV with shift
endShift, exactly as in the previous solution. However, before storing a
state in knowledge, we must canonicalize the ending window by unwinding
the circular shift. Two calls with the same logical window but different values of
endShift must map to the same entry in knowledge. The
canonicalization is done by copying endV into state.endV
with the shift corrected:
for(long i=0;i<endV.size();++i){
state.endV[(i+endShift)%endV.size()]=endV[i];
}
After the canonicalization, we search knowledge for the current state. If
it is found, we return the stored result immediately. Otherwise, we proceed with the
recursion as before, and store the computed result in knowledge before
returning.
The base case \(n=0\) is handled before the memo lookup. Since the base case always
returns 1 and is O(1) to compute, there is no benefit to storing it in
knowledge.
#include<iostream>
#include<vector>
#include"ssm.cpp"
struct Memo{
public:
std::vector<long> endV;
long n;
long numArrays;
int operator<(const Memo& ) const;
};
int Memo::operator<(const Memo& b) const{
if(n<b.n){return 1;}
if(n>b.n){return 0;}
if(endV<b.endV){return 1;}
return 0;
}
long startValueToAvoid(const std::vector<long>& forbidden,
const std::vector<long>& endV,
long endVLast){
long j=1;
while(j<forbidden.size()){
if(forbidden[j]!=endV[(j+endVLast+1)%forbidden.size()]){
return -1;
}
++j;
}
return forbidden[0];
}
long countS_DP(ssm::set<Memo>& knowledge,
const std::vector<long>& forbidden,
std::vector<long>& endV,
long endShift,
long n,
long m){
if(n<1){
return 1;
}
Memo state;
state.n=n;
state.endV.resize(endV.size());
for(long i=0;i<endV.size();++i){
state.endV[(i+endShift)%endV.size()]=endV[i];
}
long idx=knowledge.find(state);
if(idx!=-1){
return knowledge[idx].numArrays;
}
long total=0;
long i=0;
long valSave=endV[endShift];
long vToAvoid=startValueToAvoid(forbidden,
endV,
endShift+endV.size()-1);
long endShiftEmpl=(endShift+forbidden.size()-1)%forbidden.size();
while(i<m){
if(i!=vToAvoid){
endV[endShift]=i;
total+=countS_DP(knowledge,
forbidden,endV,
endShiftEmpl,
n-1,
m);
}
++i;
}
endV[endShift]=valSave;
state.numArrays=total;
knowledge.insert(state);
return total;
}
int main(){
long m,n,k;
std::cin>>m>>n>>k;
std::vector<long> forbidden(k);
std::vector<long> endV(k,-1);
for(long i=0;i<k;++i){
std::cin>>forbidden[i];
}
ssm::set<Memo> knowledge;
std::cout<<countS_DP(knowledge,forbidden,endV,k-1,n,m)<<"\n";
return 0;
}