#include<bits/stdc++.h>

namespace AVL
{
	template<class T> class multiset
	{
    	class node
    	{
        	friend class multiset; T key; size_t count, height; node *left, *right;

        	node(const T x) : key(x), count(1), height(1), left(nullptr), right(nullptr) {}

        	friend size_t Height(const node* t)
        	{
            	return t?t->height:0;
        	}

        	node* update_height()
        	{
            	height = std::max(Height(left),Height(right))+1; return this;
        	}

        	int Balance() const
        	{
            	return int(Height(left))-int(Height(right));
        	}

        	friend node* rotate_right_aux(node* y)
        	{
            	node *x = y->left, *z = x->right;

            	x->right = y, y->left = z, y->update_height();

            	return x->update_height();
        	}

        	friend node* rotate_left_aux(node* x)
        	{
            	node *y = x->right, *z = y->left;

            	y->left = x, x->right = z, x->update_height();

            	return y->update_height();
        	}

        	node* rotate_right(const T x)
        	{
            	if (x < left->key)
                	return rotate_right_aux(this);

            	if (left->key < x)
            	{
                	left = rotate_left_aux(left);

                	return rotate_right_aux(this);
            	}

            	return this;
        	}

        	node* rotate_left(const T x)
        	{
            	if (right->key < x)
                	return rotate_left_aux(this);

            	if (x < right->key)
            	{
                	right = rotate_right_aux(right);

                	return rotate_left_aux(this);
            	}

            	return this;
        	}

        	friend node* insert_key(node* t, const T x)
        	{
            	return t? t->insert(x):new node(x);
        	}

        	node* insert(const T x)
        	{
            	if (x < key)
                	left = insert_key(left,x);
            	else if (key < x)
                	right = insert_key(right,x);
            	else
            	{
                	count++; return this;
            	}

            	update_height(); int balance = Balance();

            	if (balance > 1)
                	return rotate_right(x);

            	if (balance < -1)
                	return rotate_left(x);

            	return this;
        	}

        	template<class OutputIterator>
        	OutputIterator copy_all(OutputIterator result, bool distinct) const
        	{
        		if(left)
        			result = left->copy_all(result,distinct);

        		*result++ = key;

        		if(not distinct)
                    for(size_t j = 1; j < count;++j)
                        *result++ = key;

        		if(right)
        			result = right->copy_all(result,distinct);

                return result;
        	}

			friend std::ostream& operator << (std::ostream& out, const node* t)
			{
				if (t->left)
					out << t->left << ' ';

				out << t->key;

				for(int i = 1, n = t->count; i < n; i++)
					out << ' ' << t->key;

				if (t->right)
					out << ' ' << t->right;

				return out;
			}

    	} *root;

	public:
    	multiset() : root(nullptr) {}

    	void insert(T key)
    	{
        	root = insert_key(root,key);
    	}

		template<class InputIterator>
    	void insert(InputIterator first, InputIterator last)
    	{
    	    while(first != last)
                root = insert_key(root,*first++);
    	}
    	
    	template<class OutputIterator>
    	OutputIterator copy_all(OutputIterator result, bool distinct = false) const
    	{
    		if(root)
    			result = root->copy_all(result,distinct);

            return result;
    	}

    	friend std::ostream& operator << (std::ostream& out, const multiset& t)
    	{
    		if(t.root)
    			out << t.root;

    		return out;
    	}
	};
}

namespace timer
{
    typedef std::chrono::high_resolution_clock     Clock_t;
    typedef std::chrono::time_point<Clock_t>        Time_t;
    typedef std::chrono::milliseconds         Time_scale_t;

    static Time_t present;

    inline void reset()
    {
        present = Clock_t::now();
    }

    inline size_t elapsed_time()
    {
        auto past = present;

        reset();

        return std::chrono::duration_cast<Time_scale_t>(present-past).count();
    }
}

using namespace std;

inline void time_info(const string& info, size_t t)
{
	cout << "time(" << info << ") = " << t << " msec" << endl;
}


const int m = 1e5, n = 2e6, N = 1e5; mt19937_64 Random;

vector<int> a1(n), x(n), a2(n), q(m), ans(m);

AVL::multiset<int> b1; multiset<int> b2;

int main()
{
	ios_base::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	
	for_each(q.begin(),q.end(),[](int &key){key = Random()%N;}),
    for_each(x.begin(),x.end(),[](int &key){key = 1+Random()%N;}),
    timer::reset(),
    b1.insert(x.begin(),x.end());

	const auto u_search = [](int a)
	{
		return upper_bound(a1.begin(),a1.end(),a)-a1.begin();
	};

	auto t_insert1 = timer::elapsed_time();

	b2.insert(x.begin(),x.end());

	auto t_insert2 = timer::elapsed_time();

    b1.copy_all(a1.begin());

    auto t_copy1 = timer::elapsed_time();

    copy(b2.begin(),b2.end(),a1.begin());

    auto t_copy2 = timer::elapsed_time();

    transform(q.begin(),q.end(),ans.begin(),u_search);

    auto t_upper_bound = timer::elapsed_time();

    cout << "Performance comparison using " << n << " items" << endl,
    time_info("insert-AVL-multiset",t_insert1),
    time_info("insert-std-multiset",t_insert2),
    time_info("copy-AVL-multiset",t_copy1),
    time_info("copy-std-multiset",t_copy2),
    cout << "Performance of processing " << m << " queries" << endl,
    time_info("upper-bound",t_upper_bound);
}
