Module ৮ · set আর multiset: sorted, unique, আর log n-এ খুঁজে পাওয়া
set: সাজানো, কোনো repeat নেই, আর কয়েক ধাপেই খুঁজে পাওয়া
এই lesson-এ যা শিখবে
- একটা
std::setdeclare করতে পারবে, যেকোনো ক্রমে মান insert করে সেগুলো সাজানো অবস্থায় ফেরত পাবে, প্রতিটা repeat বাদ দিয়ে। countআরfindদিয়ে "x আছে তো?" জিজ্ঞেস করতে পারবে, একটা মান erase করতে পারবে, আর*s.begin()আর*s.rbegin()দিয়ে সবচেয়ে ছোট আর সবচেয়ে বড় মানটা পড়তে পারবে।- এক বাক্যে বুঝিয়ে বলতে পারবে, একটা find কেন মাত্র কয়েক ধাপেই হয়ে যায়, আর repeat রাখতেই হলে
multisetবেছে নিতে পারবে।
Amara coding club-এর sign-up খাতাটা সামলায়, আর নামগুলো আসে যেকোনো ক্রমে, কেউ কেউ দুইবারও লেখে। ওর লাগবে আলাদা আলাদা নামগুলো, বর্ণমালার ক্রমে, আর ওদিকে Kenji বারবার জিজ্ঞেস করছে, ওর নাম তালিকায় উঠেছে কি না। Vector হলে প্রতিটা sign-up-এর পরে sort করে repeat-গুলো বাদ দিতে হতো, নয়তো প্রতিটা প্রশ্নে পুরো তালিকা ঘেঁটে দেখতে হতো। সাজানো C array-ও কাজে আসে না: প্রতিটা নতুন নামের জন্য পরের সব নাম এক ঘর করে সরাতে হয়। এই lesson Amara-কে এমন একটা container দেবে, যেটা নাম আসতে আসতেই দুটো কাজই সেরে ফেলে।
দুইটা প্রতিশ্রুতি: সাজানো, আর কোনো repeat নেই
Set হলো এমন একটা container, যেটা প্রতিটা মান বড়জোর একবার রাখে, আর মানগুলো সাজিয়ে রাখে। তুমি মান insert করো যেকোনো ক্রমে। Set-টার শুরু থেকে হাঁটলে মানগুলো বেরোয় সবচেয়ে ছোট থেকে সবচেয়ে বড় পর্যন্ত। ছোট মানে সংখ্যার বেলায় তোমার চেনা সেই <, আর string-এর বেলায় Module 3-এর byte-এর ক্রম।
Set-এ আগে থেকেই আছে, এমন একটা মান insert করলে কিছুই বদলায় না। Module 0-এ একটা set-কে দেওয়া হয়েছিল 30, 10, 20 আর 10, আর সেটা print করেছিল 10 20 30। এবার একই কথা, Amara-র নামগুলো দিয়ে।
তাই set Amara-কে একসাথে দুইটা জিনিস দেয়: নামগুলো বেরোয় সাজানো অবস্থায়, আর প্রতিটা নাম বেরোয় একবারই।
ভেতরে: একটা tree, যেটা খাটো থাকে
প্রতিটা নাম না দেখেই set কীভাবে একটা নাম খুঁজে পায়? ও মানগুলো রাখে একটা binary search tree-তে। প্রতিটা মান থাকে একটা node-এ, memory-র একটা ছোট্ট বাক্সে। কোনো node-এর চেয়ে ছোট সব মান থাকে ওর বাঁ দিকে কোথাও, আর বড় সব মান ওর ডান দিকে। খোঁজা শুরু হয় একদম উপরের node থেকে, যার নাম root; প্রতিটা node-এ একবার তুলনা করে বাঁয়ে বা ডানে যায়, আর একটা গোটা দিক পেছনে পড়ে থাকে।
ছবিতে সাতটা মানের মধ্যে 50 খোঁজা হচ্ছে। সাজানো array একটা একটা করে দেখে 10, 20, 30, 40, তারপর 50: পাঁচ ধাপ। Tree-তে তুলনা হয় 40-এর সাথে, তারপর 60, তারপর 50-এর সাথে: তিন ধাপ। সাতটা মান 40, 20, 60, 10, 30, 50 আর 70 ক্রমে এলে GCC 12-এর library হুবহু এই tree-টাই বানায়। অন্য ক্রমে এলে আকারও হয় অন্যরকম, পরের অংশে সেটা দেখবে।
Tree-তে প্রতিটা নতুন level মানের জন্য জায়গা দ্বিগুণ করে দেয়, তাই 20টা level-এ দশ লাখের বেশি মান ধরে। দশ লাখ মানের সবচেয়ে খাটো tree-র level 20টা, আর ওতে একটা find-এ লাগে বড়জোর 20 ধাপ। Module 2-এর find একটা vector-এ খুঁজতে গিয়ে দশ লাখ ধাপও নিতে পারে।
যে tree-কে ওই সবচেয়ে খাটো আকারের কাছাকাছি রাখা হয়, তাকে বলে balanced। প্রতিটা insert আর erase-এর পরে library ওর tree আবার balance করে নেয়। আসল tree সবচেয়ে খাটোটার চেয়ে উঁচু হতে পারে, তবে বড়জোর দ্বিগুণ; lesson 05 সেটা মেপে দেখাবে। তাই একটা find-এ প্রতি level-এ একটা করে তুলনা লাগে, আর library level-এর সংখ্যা কম রাখে।
Tree-টাকে বাড়তে দেখো, একটা একটা insert করে
GCC-র standard library, যার নাম libstdc++, set রাখে একটা red-black tree-তে। প্রতিটা node সাথে একটা রংও রাখে, লাল বা কালো। প্রতিটা insert বা erase-এর পরে library কয়েকটা node-এর রং বদলায় আর কয়েকটাকে ঘুরিয়ে বসায়, যাতে রঙের নিয়মগুলো ঠিক থাকে। Widget-টা প্রতি ধাপের পরে আসল আকারটাই দেখায়, শুধু রংগুলো বাদ দিয়ে। C++ standard কোনো tree-র নামই বলে না: ও শুধু কথা দেয়, find, insert আর erase-এ O(log n) ধাপ লাগবে।
O(log n) মানে n দ্বিগুণ হলে মোটামুটি একটা ধাপ বাড়ে, Module 0 যেমন বলেছিল। এবার দেখো 10 থেকে 80 আসছে ছোট থেকে বড় ক্রমে; যে tree কখনো balance করে না, তার জন্য এটাই সবচেয়ে খারাপ ক্রম।
ছোট থেকে বড় ক্রমে আসা আটটা মান দিয়ে level হলো 4টা। যে সাধারণ tree কখনো balance করে না, সে প্রতিটা মান আগেরটার ডানে ঝোলাত: 8 level গভীর একটা chain। Root-টা খেয়াল করো: ও 10 থেকে সরে গেল 20-এ, তারপর 40-এ। দ্বিতীয়বারের insert(50) নিচে নেমে আগে থেকে থাকা 50 পর্যন্ত গেল, কিছুই বদলাল না। find(35) 30-এর নিচে গিয়ে tree থেকে পড়ে গেল, তাই 35 set-এ নেই।
তাই আকারটা library-র পছন্দ, আর মান যে ক্রমেই আসুক, tree থাকে মোটে কয়েক level গভীর।
প্রথম দিনেই যে syntax লাগবে
std::set আর std::multiset, আর ওদের রোজকার call-গুলো
#include <set>
set<T> s; an empty set of T
set<T> s{a, b, c}; a set made from a list; repeats are dropped
s.insert(x); add x; nothing happens if x is already there
s.erase(x); remove x, if it is there
s.count(x) 1 if x is there, 0 if not
s.find(x) an iterator to x, or s.end() if x is not there
s.size() s.empty() how many values, and whether there are none
*s.begin() *s.rbegin() the smallest and the largest value
for (T x : s) every value, from the smallest to the largest
multiset<T> m; the same calls, but repeats are kept
set<T>: angle bracket-এ একটাই type, মানগুলোর type, ঠিক vector-এর মতো।<set>header আনেsetআরmultisetদুটোই।insert,erase,countআরfind, সবাই একটা মান নেয়। কোন মান কোথায় বসবে, তুমি কখনো বলো না: set নিজেই ঠিক করে।findফেরত দেয় একটা iterator, মানে container-এর একটা জায়গার সেই চিহ্ন, যেটা Module 2-এ দেখেছ।s.end()হলো শেষ মানের ঠিক পরের জায়গা, তাই এখানে এর মানে "পাওয়া যায়নি"।*s.begin()আর*s.rbegin(): শুধু set খালি না থাকলে। আগেempty()check করো।
তাই রোজকার set-এর code মান ধরে কাজ করে, জায়গার নম্বর ধরে কখনো না।
insert, আর সাজানো ক্রমে হাঁটা
s.insert(x) x-কে ওর সাজানো জায়গায় বসায়। একটা range-for set-টার উপর দিয়ে হাঁটে, সবচেয়ে ছোট মান থেকে সবচেয়ে বড় পর্যন্ত। এই program প্রতিটা insert-এর পরে পুরো set print করে।
#include <iostream>
#include <set>
using namespace std;
void show(const char* step, const set<int>& s) {
cout << step << ':';
for (int x : s) {
cout << ' ' << x;
}
cout << " (size " << s.size() << ")\n";
}
int main() {
set<int> s;
s.insert(30);
show("insert(30)", s);
s.insert(10);
show("insert(10)", s);
s.insert(20);
show("insert(20)", s);
s.insert(10);
show("insert(10)", s);
return 0;
}
insert(30): 30 (size 1)
insert(10): 10 30 (size 2)
insert(20): 10 20 30 (size 3)
insert(10): 10 20 30 (size 3)
মানগুলো এসেছিল 30, 10, 20 ক্রমে, তবু 10 বসল 30-এর আগে, আর 20 ওদের মাঝখানে। দ্বিতীয় insert(10) দেখল 10 আগে থেকেই আছে, তাই size রইল 3। তাই insert যে ক্রমেই হোক, হাঁটলে সবসময় সাজানোই পাওয়া যায়।
আছে তো? count আর find
s.count(x) বলে set-এ x-এর কয়টা copy আছে। Set-এ সেটা শুধু 1 বা 0 হয়, তাই এটাকে হ্যাঁ বা না হিসেবে পড়া যায়। s.find(x) ফেরত দেয় x-এর দিকে একটা iterator, আর x না থাকলে s.end()।
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s{3, 9, 14, 27, 31};
cout << s.count(14) << ' ' << s.count(5) << '\n';
auto it = s.find(27);
if (it != s.end()) {
cout << "found " << *it << '\n';
}
if (s.find(5) == s.end()) {
cout << "5 is not there\n";
}
return 0;
}
1 0
found 27
5 is not there
* দিয়ে পড়ার আগে find-এর উত্তর end()-এর সাথে মিলিয়ে নাও। *s.end() পড়া হলো undefined behaviour: কী হবে, C++ standard কিছুই বলে না। তাই সোজা হ্যাঁ বা না-এর জন্য count নাও, আর জায়গাটাও লাগলে find।
মান ধরে erase
s.erase(x) x-কে সরিয়ে দেয়, যদি set-এ x থাকে। না থাকলে কিছুই হয় না।
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s{3, 9, 14, 27, 31};
s.erase(14);
s.erase(5);
cout << "left:";
for (int x : s) {
cout << ' ' << x;
}
cout << " (size " << s.size() << ")\n";
return 0;
}
left: 3 9 27 31 (size 4)
erase(14) 14-কে সরাল। erase(5) কোনো 5 পেল না, কিছুই করল না, কোনো error-ও দিল না। তাই নেই এমন মান erase করা নিরাপদ, কিন্তু নেই এমন মান পড়া নিরাপদ না।
সবচেয়ে ছোট আর সবচেয়ে বড়
s.begin() হলো সবচেয়ে ছোট মানের জায়গা, তাই *s.begin() সেটা পড়ে। s.rbegin() হলো উল্টো দিকে হাঁটার শুরু, r মানে reverse, তাই *s.rbegin() হলো সবচেয়ে বড় মান।
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> temps{31, 28, 35, 29};
cout << "coolest " << *temps.begin() << ", hottest " << *temps.rbegin() << '\n';
temps.insert(38);
temps.erase(28);
cout << "coolest " << *temps.begin() << ", hottest " << *temps.rbegin() << '\n';
set<int> none;
if (none.empty()) {
cout << "an empty set has no coolest\n";
}
return 0;
}
coolest 28, hottest 35
coolest 29, hottest 38
an empty set has no coolest
Insert আর erase-এর পরে দুই মাথাই সরে গেল, অথচ program ওদের একবারও খুঁজতে যায়নি। খালি set-এর কোনো সবচেয়ে ছোট বা সবচেয়ে বড় মান নেই। ওখানে *s.begin() আর *s.rbegin() undefined, আর একবার চালাতে কী হয়েছিল, সেটা দেখাবে lesson 06। তাই কোনো মাথা পড়ার আগে empty() check করো।
Index নেই, আর জায়গায় বসে মান বদলানোও নেই
Vector-এর জায়গার নম্বর আছে, তাই v[0] ওর প্রথম element। Set-এর জায়গার কোনো নম্বর নেই, তাই index দিলে compile-ই হয় না।
set<int> s{10, 20, 30};
cout << s[0] << '\n';
Runner-এর flag-এ GCC 12 থেমে যায় error: no match for 'operator[]' (operand types are 'std::set<int>' and 'int') দিয়ে। তৃতীয় মানে পৌঁছাতে begin() থেকে হেঁটে যেতে হয়, কীভাবে, সেটা দেখাবে lesson 03। Iterator দিয়ে মান বদলানোও compile হয় না।
set<int> s{10, 20, 30};
auto it = s.find(20);
*it = 25;
GCC 12 বলে error: assignment of read-only location 'it.std::_Rb_tree_const_iterator<int>::operator*()'। কারণটা tree, যেটা মান ধরে সাজানো। 20-কে জায়গায় বসেই 25 বানালে ও ভুল জায়গায় বসে থাকবে, আর পরের খোঁজগুলো ভুল পথে যাবে। তাই library প্রতিটা মান দেয় read-only করে: পড়তে পারবে, কিন্তু লিখতে কখনো না।
তাই set বদলাও শুধু insert আর erase দিয়ে: আগে পুরোনো মানটা erase করো, তারপর নতুনটা insert। Node-টা রেখেই এই কাজ করার একটা উপায় দেখাবে lesson 02।
multiset: যে set repeat-ও রাখে
কখনো কখনো repeat-গুলোই আসল কথা। ক্লাস কোথায় বেড়াতে যাবে, সেই ভোটে প্রতিটা ভোট লাগে, এক জায়গার জন্য একটা না। Multiset হলো এমন একটা set, যেটা repeat-ও রাখে। একই নিয়মে সাজায়, একই header-এ থাকে, call-গুলোও একই। ওর count(x) বলে x-এর কয়টা copy আছে।
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main() {
multiset<string> votes{"zoo", "museum", "zoo", "park", "zoo"};
cout << "votes:";
for (const string& v : votes) {
cout << ' ' << v;
}
cout << " (size " << votes.size() << ")\n";
cout << "zoo: " << votes.count("zoo") << ", lake: " << votes.count("lake") << '\n';
return 0;
}
votes: museum park zoo zoo zoo (size 5)
zoo: 3, lake: 0
Zoo-র তিনটা ভোটই রইল, পাশাপাশি, কারণ সাজানো ক্রমে সমান মানগুলো গায়ে গায়ে বসে। এগুলোর মধ্যে শুধু একটা সরাতে আলাদা একটা call লাগে, সেটা দেখাবে lesson 02। তাই প্রতিটা copy লাগলে multiset নাও, আর একটা হলেই চললে set।
সবচেয়ে ছোট set program: একটা repeat-সহ পাঁচটা সংখ্যা ঢোকে, আর বেরোয় সাজানো অবস্থায়, এক লাইনে, size-সহ।
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s;
for (int x : {42, 7, 19, 7, 3}) {
s.insert(x);
}
cout << "sorted:";
for (int x : s) {
cout << ' ' << x;
}
cout << '\n' << s.size() << " different values\n";
return 0;
}
sorted: 3 7 19 42
4 different values
দুইবার আসা 7 print হলো একবার, আর size-ও ওকে গুনল একবার। প্রথম loop হাঁটে code-এর ভিতরেই লেখা একটা তালিকার উপর দিয়ে, আর দ্বিতীয়টা set-এর উপর দিয়ে।
Run in CompilerAmara-র পাঁচটা sign-up যায় একটা set-এ, Bob দুইবার। Kenji জিজ্ঞেস করে, ও তালিকায় আছে কি না; Zara-ও একই কথা জিজ্ঞেস করে। তারপর Kenji নাম লেখায়।
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main() {
set<string> signed_up;
for (string name : {"maria", "bob", "zara", "bob", "amara"}) {
signed_up.insert(name);
}
for (string who : {"kenji", "zara"}) {
if (signed_up.count(who) == 1) {
cout << who << " is on the list\n";
} else {
cout << who << " is not on the list yet\n";
}
}
signed_up.insert("kenji");
auto it = signed_up.find("kenji");
if (it != signed_up.end()) {
cout << "now " << *it << " is on it too\n";
}
cout << signed_up.size() << " different people so far\n";
return 0;
}
kenji is not on the list yet
zara is on the list
now kenji is on it too
5 different people so far
Bob-কে গোনা হলো একবার, তাই Kenji-র sign-up-এর পরে আলাদা মানুষ 5 জন, 6 না। দুইটা প্রশ্নের উত্তর দিল count, আর find ফেরত দিল set-এ রাখা নামটা। দশ লাখ নাম থাকলেও প্রতিটা প্রশ্নে লাগত মোটে কয়েক ডজন ধাপ।
Kenji স্কুলের নাটকের জন্য সিট বুক করে। সিটের নম্বর শুরু হয় মঞ্চের দিক থেকে, তাই বুক করা সবচেয়ে ছোট নম্বরটাই মঞ্চের সবচেয়ে কাছের সিট। একজন বুকিং বাতিল করে, আর নতুন একজন 30 নম্বর সিট বুক করে।
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> booked{12, 3, 27, 8, 15};
booked.erase(3);
booked.insert(30);
cout << "booked:";
for (int seat : booked) {
cout << ' ' << seat;
}
cout << '\n';
cout << "nearest the stage: " << *booked.begin() << ", farthest: " << *booked.rbegin() << '\n';
if (booked.count(3) == 0) {
cout << "seat 3 is free again\n";
}
return 0;
}
booked: 8 12 15 27 30
nearest the stage: 8, farthest: 30
seat 3 is free again
বাতিল হওয়া 3 নম্বর সিট set থেকে চলে গেল, তাই এখন সবচেয়ে কাছের বুকিং 8 নম্বর সিট। 30 বুক হওয়ার সাথে সাথেই সবচেয়ে দূরের সিট হয়ে গেল 30। বুকিং বদলাতে থাকলেও set দুই মাথাই হাতের কাছে রেখেছে।
Run in CompilerMaria ওর ক্লাসের quiz-এর প্রতিটা নম্বর চায়, সমান নম্বরগুলোসহ, তাই ও multiset নেয়। তুলনার জন্য পাশে একই নম্বরের একটা set-ও রাখা আছে।
#include <iostream>
#include <set>
using namespace std;
int main() {
multiset<int> marks{85, 72, 85, 90, 64, 85, 72};
set<int> different{85, 72, 85, 90, 64, 85, 72};
cout << "every mark:";
for (int m : marks) {
cout << ' ' << m;
}
cout << '\n';
cout << marks.count(85) << " students scored 85\n";
cout << "lowest " << *marks.begin() << ", highest " << *marks.rbegin() << '\n';
cout << "the multiset keeps " << marks.size() << ", a set keeps " << different.size() << '\n';
return 0;
}
every mark: 64 72 72 85 85 85 90
3 students scored 85
lowest 64, highest 90
the multiset keeps 7, a set keeps 4
তিনজন ছাত্রছাত্রী 85 পেয়েছে, আর multiset তিনটাই রেখেছে। Set রেখেছে 4টা নম্বর, প্রতিটা একবার করে। সমান নম্বরের হিসাব দিতে Maria ঠিক এই program-টাই লিখবে: এখানে count(85) হ্যাঁ-না উত্তর না, এটা ছাত্রছাত্রীর সংখ্যা।
এটা কোথায় কাজে লাগে
- Linux kernel-এর red-black tree.
lib/rbtree.cহলো kernel-এর নিজের balanced tree, kernel-এর নানা জায়গায় এটা কাজে লাগে। CFS scheduler, 2.6.23 থেকে 6.5 পর্যন্ত যেটা ছিল Linux-এর default, চালানোর জন্য তৈরি task-গুলো এমন একটা tree-তে রাখত, virtual run time ধরে সাজিয়ে। ও চালাত সবচেয়ে বাঁয়ের task-টা, মানে যার virtual run time সবচেয়ে কম। - Linux-এর epoll. যে server একসাথে অনেক network connection-এর অপেক্ষায় থাকে, সে connection-গুলো epoll-এর হাতে দেয়। epoll যে file descriptor-গুলোর উপর নজর রাখে, সেগুলো রাখে একটা red-black tree-তে, তাই একটা যোগ করা, খোঁজা বা সরাতে লাগে
O(log n)ধাপ। - Java-র
TreeSet. Java-র সাজানো set ওর মানগুলো রাখে একটা red-black tree-তে। ওর documentationadd,removeআরcontains-এর জন্যlog(n)সময়ের কথা দেয়; C++insert,eraseআরfind-এর জন্য ঠিক এই কথাটাই দেয়।
যে ভুলগুলো সবাই করে
১. Range-for-এ প্রতিটা মান বদলাতে যাওয়া।
set<int> prices{10, 20, 30};
for (int& p : prices) {
p += 5;
}
Runner-এর flag-এ error: error: binding reference of type 'int&' to 'const int' discards qualifiers। Set-এর মান read-only, তাই যে reference ওদের বদলাতে পারত, সেটা বাতিল হয়ে যায়। বদলানো মানগুলো দিয়ে বরং নতুন একটা set বানাও। তুমি এটা চেষ্টা করবে, কারণ Module 1-এ এই একই loop একটা vector বদলে দিয়েছিল।
২. Header লিখতে ভুলে যাওয়া।
#include <iostream>
using namespace std;
int main() {
set<int> s{3, 1, 2};
cout << s.size() << '\n';
return 0;
}
Runner-এর flag-এ error: error: 'set' was not declared in this scope, তারপর GCC 12 নিজেই সমাধান বলে দেয়: note: 'std::set' is defined in header '<set>'; did you forget to '#include <set>'?। multiset-এরও একই header লাগে। তুমি এটা ভুলবে, কারণ set-টা তুমি যোগ করো program-এর মাঝখানে, include-গুলো থেকে অনেক দূরে।
৩. Insert-এর ক্রমটাই ফেরত পাওয়ার আশা করা।
set<string> steps;
steps.insert("wash");
steps.insert("cut");
steps.insert("cook");
for (const string& s : steps) {
cout << s << '\n';
}
Runner-এর flag-এ কোনো বার্তা নেই, আর print হয় cook, cut আর wash: বর্ণমালার ক্রমে, রান্নার ধাপের ক্রমে না। মানগুলো কোন ক্রমে এসেছিল, set তার কোনো হিসাব রাখে না। ক্রমটা জরুরি হলে ধাপগুলো একটা vector-এ রাখো। তুমি ক্রমটা ফেরত পাওয়ার আশা করবে, কারণ vector, deque আর list, সবাই সেটা রেখেছিল।
Amara club-এর কাগজের sign-up form-টা computer-এ তুলেছে, প্রতি লাইনে একটা নাম, যে ক্রমে লোকে লিখেছিল। কেউ কেউ দুইবার নাম লিখেছে, কয়েকজন তিনবার। Welcome email-এর জন্য ওর লাগবে আলাদা মানুষ কয়জন, আর তাদের নাম বর্ণমালার ক্রমে।
Input. এক লাইনে n, তারপর n-টা নাম, প্রতি লাইনে একটা।
Output. প্রথমে এক লাইনে আলাদা নাম কয়টা। তারপর প্রতিটা আলাদা নাম নিজের লাইনে, বর্ণমালার ক্রমে।
Constraints. 1 <= n <= 90000। একটা নাম 1 থেকে 10টা ছোট হাতের ইংরেজি অক্ষর। বর্ণমালার ক্রম মানে string যে ক্রমে তুলনা করে, তাই al আসে alice-এর আগে।
Sample. Input 6, তারপর maria, bob, zara, bob, amara আর maria দিলে 4, তারপর amara, bob, maria আর zara। Bob আর Maria দুজনেই দুইবার করে নাম লিখেছে।
#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n = 0;
cin >> n;
set<string> names; // every different name, kept in alphabetical order
for (int i = 0; i < n; i++) {
string name;
cin >> name;
// Add name to names.
}
// Print how many different names there are, then every name in
// alphabetical order, one per line.
return 0;
}
club-sign-ups নামে grade হয়। এটা এই module-এর problem set-এর একটা free problem। Hidden test-এ আছে একটামাত্র নাম, 1000 বার লেখা একই নাম, আর এমন নাম, যেগুলো দিয়ে অন্য নাম শুরু হয়, যেমন al আর alice। আরও আছে 90000টা আলাদা নাম।
Maria-র ক্লাস আর David-এর ক্লাস একই quiz দিয়েছে, নম্বর 100-এর মধ্যে। শিক্ষকেরা চান সেই নম্বরগুলো, যেগুলো দুই ক্লাসেই আছে, প্রতিটা একবার করে, যাতে সেই নম্বর পাওয়া ছাত্রছাত্রীদের মিলিয়ে দেখা যায়।
Input. এক লাইনে n, তারপর এক লাইনে প্রথম ক্লাসের n-টা নম্বর। তারপর এক লাইনে m, তারপর এক লাইনে দ্বিতীয় ক্লাসের m-টা নম্বর।
Output. এক লাইন: যে নম্বরগুলো দুই ক্লাসেই আছে, প্রতিটা একবার, সবচেয়ে কম থেকে উপরের দিকে, মাঝে একটা করে space। দুই ক্লাসে মেলে এমন কোনো নম্বর না থাকলে none শব্দটা print করো।
Constraints. 1 <= n, m <= 100000। প্রতিটা নম্বর 0 থেকে 100-এর একটা পূর্ণসংখ্যা।
Sample. Input 5, 70 85 85 40 92, 4 আর 85 60 92 92 দিলে 85 92। দুইটা নম্বরই দুই ক্লাসে আছে, আর প্রতিটা print হয় একবার।
#include <iostream>
#include <set>
using namespace std;
int main() {
int n;
cin >> n;
set<int> first;
for (int i = 0; i < n; i++) {
int mark;
cin >> mark;
first.insert(mark);
}
int m;
cin >> m;
set<int> second;
for (int i = 0; i < m; i++) {
int mark;
cin >> mark;
second.insert(mark);
}
// Walk first from its lowest mark. Print every mark that second also
// holds, on one line, separated by single spaces, or the word none.
return 0;
}
আলাদা করে grade হয় না। একটা set-এর উপর দিয়ে ক্রমে হাঁটো, আর প্রতিটা নম্বরের উত্তর দিক অন্য set-এর count; হাঁটাটাই নম্বরগুলো ছোট থেকে বড় ক্রমে দিয়ে দেয়।
Kenji-র arcade machine একটা board দেখায়: এ পর্যন্ত খেলা সবচেয়ে বেশি পাঁচটা আলাদা score। একই score-এর দুইটা খেলা board-এ একটাই জায়গা পায়। প্রতিটা খেলার পরে machine দেখায় board-এর সবচেয়ে কম score-টা। Board ভরে গেলে, board-এ উঠতে হলে নতুন খেলাকে ওটা টপকাতে হয়।
Input. এক লাইনে n, তারপর n-টা score, প্রতিটা খেলার একটা করে, যে ক্রমে খেলা হয়েছে।
Output. প্রতিটা খেলার জন্য এক লাইন: সেই খেলার পরে board-এর সবচেয়ে কম score।
Constraints. 1 <= n <= 100000। প্রতিটা score 0 থেকে 1000000000-এর মধ্যে। প্রতিটা খেলার পরে তোমার set-এ বড়জোর পাঁচটা score থাকবে।
Sample. Input 7 আর 50 80 50 90 10 70 60 দিলে 50, 50, 50, 50, 10, 10 আর 50। ষষ্ঠ খেলায় board ভরে যায়, তাই সপ্তম খেলার 60 এসে 10-কে board থেকে নামিয়ে দেয়।
#include <iostream>
#include <set>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
set<int> board; // the highest different scores, never more than five
for (int i = 0; i < n; i++) {
int score;
cin >> score;
// Put score on the board, keep only the five highest, then print
// the lowest score left on the board.
}
return 0;
}
আলাদা করে grade হয় না। Lesson যে কথাটা শুধু ইঙ্গিতে বলেছে: s.erase(s.begin()) সবচেয়ে ছোট মানটা সরায়, তাই প্রতিটা insert-এর পরে একটা erase-ই board-কে পাঁচে ধরে রাখে।
সচরাচর যে প্রশ্নগুলো আসে
আমি তো অন্য ক্রমে insert করেছিলাম, set-টা সাজানো হলো কেন?
কারণ প্রতিটা insert ওর মানটা tree-র ভিতরে সাজানো জায়গায় বসায়। তুমি কোন ক্রমে insert করেছিলে, সেটা কোথাও রাখা থাকে না। পরে ক্রমটা লাগলে মানগুলো একটা vector-এও রেখে দাও।
একটা set কি সমান দুইটা মান রাখতে পারে?
না। দ্বিতীয় insert প্রথম মানটা খুঁজে পায়, আর কিছুই বদলায় না। Multiset দুটোই রাখে, আর ওর
countবলে কয়টা আছে।আমি
s[2]লিখতে পারি না কেন?Set-এর গোনার মতো কোনো জায়গার নম্বর নেই, কারণ ওর মানগুলো থাকে tree-র node-এ, এক সারিতে না। তৃতীয় মানে যেতে
begin()থেকে শুরু করে দুই ধাপ এগোও, lesson 03 সেটা দেখাবে।countকিfind-এর চেয়ে ধীর?Set-এ না: দুটোই একই tree বেয়ে নামে,
O(log n)ধাপে। Multiset-এcountপ্রতিটা copy-ও গোনে, তাই বেশি সময় নিতে পারে, আর ওর খরচ বলবে lesson 02।
মূল কথা
- Set প্রতিটা মান একবার রাখে, আর insert যে ক্রমেই হোক,
<ধরে সাজিয়ে ফেরত দেয়। insertআরeraseএকটা মান নেয়;count(1 বা 0) আরfind(একটা iterator, নয়তোend()) জিজ্ঞেস করে মানটা আছে কি না।*s.begin()হলো সবচেয়ে ছোট মান আর*s.rbegin()সবচেয়ে বড়; দুটোর আগেইempty()check করো।- মানগুলো থাকে একটা balanced tree-তে, তাই একটা find-এ প্রতি level-এ এক ধাপ, আর 20টা level-এ দশ লাখের বেশি মান ধরে।
- Set-এ index নেই, আর ওর মানগুলো read-only;
multisetrepeat রাখে, আর ওরcountবলে কয়টা। - আরও গভীরে যেতে চাইলে: Under the Hood, tree-র node আর যে iterator টিকে থাকে (Pro)।
এরপর lesson 02 set আর multiset-এর প্রতিটা operation একটা একটা করে দেখাবে, প্রতিটার খরচসহ।
lesson ১ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: set আর multiset-এর প্রতিটা operation, একটা একটা করে, খরচসহ