Learn C++ STL

lesson ৭ / ৯ · vector: যে container-টা সবার আগে হাতে আসে

Module ২ · vector: যে container-টা সবার আগে হাতে আসে

Problem: vector

FreeProblem

এই lesson-এ যা শিখবে

  • যেকোনো input প্রতিটা starter-এর ধাঁচে একটা vector-এ পড়তে পারবে, আর একটা করে space আর '\n' দিয়ে print করতে পারবে।
  • Statement-এর constraints দেখে প্রতিটা মান, যোগফল আর count-এর জন্য int না long long, সেটা বাছতে পারবে।
  • সবচেয়ে বড় input-এ নিজের প্রথম idea-র কাজ গুনতে পারবে, আর loop-এর ভিতরের erase বা যোগফলকে এক pass দিয়ে বদলাতে পারবে।

দশটা problem, hidden test দিয়ে গ্রেড হয়। প্রতিটা এই module-এর lesson 01 থেকে 03, নয়তো CP pack-এর কোনো একটা idea-র অনুশীলন। Problem 1, 2 আর 5 ফ্রি। বাকি সাতটা খোলে Learn Pro বা track কিনলে।

Bob sample পড়ে, একটা loop লেখে, আর submit করে দেয়। Zara আগে চালিয়ে দেখে n = 1, সব মান সমান, আর এমন case যেখানে কিছুই বাকি থাকে না। এই set-এ Zara-র এই অভ্যাসটাই নম্বর আনে।

প্রতিটা starter-এর চেহারা একই

প্রতিটা starter পরপর চারটা কাজ করে। Fast input আর output চালু করে, input একটা vector-এ পড়ে, তোমার code-এর জন্য জায়গা ফাঁকা রাখে, তারপর 0 return করে। Fast লাইন দুইটা হলো ios::sync_with_stdio(false); আর cin.tie(nullptr);, Module 1 থেকে। এই set-এর সবচেয়ে বড় test প্রায় 1 MB text, তাই পড়ার গতি এখানে গুরুত্বপূর্ণ।

Vector-এর নাম রাখা হয় গল্প দেখে: marks, scores, steps। Count আগে এলে starter বানায় vector<int> scores(n), আর পড়ে scores[i]-তে। Count না থাকলে vector খালি অবস্থায় শুরু হয়, আর প্রতিটা মানের জন্য push_back call হয়, যতক্ষণ না input শেষে cin >> x fail করে।

Judge কখনো তোমার vector-এর ভিতরে তাকায় না। ও একটা hidden input দিয়ে পুরো program চালায়, আর program যা print করে সেটা লাইন ধরে ধরে মেলায়। লাইনের শেষে বাড়তি space থাকলে judge সেটা ধরে না। empty আর none-এর মতো শব্দ statement-এ যেমন লেখা, হুবহু তেমন print করো, ছোট হাতের অক্ষরে। প্রতিটা লাইন শেষ করো '\n' দিয়ে।

Constraints-ই ঠিক করে int না long long

একটা int-এ ধরে 2147483647 পর্যন্ত, মানে 2 x 109-এর একটু বেশি। এই set-এর প্রতিটা আলাদা মান এতে ধরে, কারণ কোনোটাই 109-এর বড় না। যোগফল আর count কিন্তু আলাদা ব্যাপার। Type বাছার আগে সবচেয়ে বড় মানকে সবচেয়ে বড় count দিয়ে গুণ করে দেখো।

Problemকী বড় হতে পারেসবচেয়ে বড় মাপType
frequency-tableএকটা mark-এর count200000int
prefix-range-sums200000 দিন পর্যন্ত মোট2 x 1014long long
grid-row-col-sums500 মানের একটা সারি বা কলাম5 x 1011long long
pair-sum-countt থেকে একটা card বাদ3 x 109long long
pair-sum-countজোড়ার সংখ্যা19999900000long long

চতুর্থ সারিটা অনেককে অবাক করে। দুইটা card যোগ করলে বড়জোর 2000000000 হয়, যেটা কোনোমতে ধরে। কিন্তু একটা card-এর যে সঙ্গী লাগে, সেটা হলো t থেকে ওই card বাদ, আর 2 x 109 থেকে -109 বাদ দিলে হয় 3 x 109। t-কে long long হিসেবে পড়ো আর long long-এ যোগ করো: খরচ শূন্য, আর প্রশ্নটাই উঠে যায়।

প্রথম idea, সবচেয়ে বড় input-এ

প্রতিটা problem একটা test-এ সময় দেয় 1 সেকেন্ড, পুরো program-এর জন্য, input পড়াসহ। Submit করার আগে সবচেয়ে বড় input-এ তোমার loop-এর ধাপ গুনে নাও। 200000টা মানের উপর একবার হাঁটা কিছুই না। যে loop প্রতি ধাপে vector-এর বাকিটা সরায়, সেটা 200000 গুণ 200000, আর সেটা অনেক বেশি।

Vector-এর দুইটা operation এমন একটা loop লুকিয়ে রাখে। Index i-তে erase i-এর পরের প্রতিটা মানকে এক ঘর বাঁয়ে সরায়, আর insert সরায় এক ঘর ডানে। একবার call করলে খরচ চলে। Vector-এর উপর চলা loop-এর ভিতরে call করলে চলে না। প্রশ্নের loop-এর ভিতরে লেখা যোগফলেরও একই দশা।

নিচের table-এ এই set-এর দুইটা জোড়া। প্রতিটা program নিজের data নিজে বানিয়েছে, আর chrono::steady_clock দিয়ে শুধু কাজটুকুর সময় মেপেছে। প্রতিটা সময় Compiler Explorer-এ GCC 12.2, g++ -O2 -std=c++17 দিয়ে একবার চালিয়ে মাপা, Playground-এর flag-ও এগুলোই। Compiler Explorer প্রায় 20 সেকেন্ড পরে program থামিয়ে দেয়।

Problemউপায়Inputসময়
drop-negativesপ্রতিটা negative মান যেখানে আছে সেখানেই erasen = 200000, সব reading negative20503 ms পরে থামিয়ে দেওয়া হয়েছে
drop-negativesপ্রতিটা negative মান যেখানে আছে সেখানেই erasen = 200000, প্রায় অর্ধেক negative20263 ms পরে থামিয়ে দেওয়া হয়েছে
drop-negativeserase-remove, এক passn = 200000, সব reading negative0.2 ms
drop-negativesপ্রতিটা negative মান যেখানে আছে সেখানেই erasen = 20000, সব reading negative297 ms
prefix-range-sumsপ্রতিটা প্রশ্নের দিনগুলো যোগ করাn = q = 200000, এলোমেলো l আর r10750 ms
prefix-range-sumsপ্রতিটা প্রশ্নের দিনগুলো যোগ করাn = q = 200000, প্রতিটা প্রশ্ন দিন 1 থেকে n20786 ms পরে থামিয়ে দেওয়া হয়েছে
prefix-range-sumsএকবার ভরে রাখা prefix vectorn = q = 200000, এলোমেলো l আর r1.8 ms
প্রথম idea বনাম এক pass: দুইটা জোড়া, log scale-এ সময়, log scale (প্রতি grid লাইনে দশ গুণ), GCC 12.2 -O2, প্রতিটা একবার 0.1 ms 1 ms 10 ms 100 ms 1 s 10 s 100 s drop-negatives, n = 200000 loop-এ erase 20,503+ ms erase-remove 0.2 ms prefix-range-sums, n = q = 200000 প্রতি প্রশ্নে যোগ 10,750 ms prefix vector 1.8 ms দুই জোড়াতেই এক pass-এর উপায় হাজার গুণেরও বেশি দ্রুত, আর judge সময় দেয় 1 সেকেন্ড।

এই কারণেই drop-negatives মাত্র 20000টা reading দেয়। সাবধানে লেখা erase loop ওখানে পাশ করে, 297 ms-এ। দশ গুণ মাপে ওর লাগে একশো গুণ কাজ। প্রতিটা ধীর version-ই ঠিক উত্তর দেয়। শুধু প্রতিটাই এক সেকেন্ড থেকে অনেক দূরে থাকে।

এই দশটা problem-এ যে রূপগুলো লাগে

vector<int> v;                         empty; grows with push_back
while (cin >> x) { v.push_back(x); }   read until the input ends
vector<int> v(n);                      n zeros; then cin >> v[i]
v.size()   v.empty()   v[i]            count, "is it empty?", index i
v.back()   v.pop_back()                the last value; remove it (never on empty)
v.insert(v.begin() + i, x);            x lands at index i; the rest move right
v.erase(remove_if(v.begin(), v.end(), f), v.end());   drop every value f says yes to
vector<int> count(101, 0);             one counter for each value 0 to 100
vector<long long> prefix(n + 1, 0);    prefix[k]: the total of the first k values
vector<vector<int>> g(r, vector<int>(c));   a grid; g[i][j] is row i, column j
sort(v.begin(), v.end());              smallest first
v.erase(unique(v.begin(), v.end()), v.end());   one copy of each run of equal neighbours
  • নিচের প্রতিটা starter statement-এর বলা vector-টায় input পড়ে রাখে। ওই লাইনগুলো যেমন আছে তেমন রাখো, আর নিজের code লেখো comment যেখানে বলে সেখানে।
  • remove_if, sort আর unique থাকে <algorithm>-এ। এদের কোনোটা ব্যবহার করলে ওই include-টা যোগ করো।
  • Sample-এর আগে কিনারাগুলো test করো: n = 1, সব মান সমান, সব মান negative, কিছুই বাকি না থাকা, আর সবচেয়ে বড় n।
Example 1: input শেষ না হওয়া পর্যন্ত পড়া, তারপর vector-এর উপর দিয়ে হাঁটা

David রোজ ওর দোকানে কতজন এল সেটা গোনে, কিন্তু কয় দিনের হিসাব সেটা কখনো বলে না। কয় দিন, মোট কতজন, আর রোজকার গোনা ক্রমমতো print করো।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    vector<int> visitors;
    int x = 0;
    while (cin >> x) {
        visitors.push_back(x);
    }

    // Many days of big counts can pass an int, so the total is long long.
    long long total = 0;
    for (int v : visitors) {
        total += v;
    }
    cout << visitors.size() << " days, " << total << " visitors\n";

    for (size_t i = 0; i < visitors.size(); i++) {
        if (i > 0) {
            cout << ' ';
        }
        cout << visitors[i];
    }
    cout << '\n';
    return 0;
}
5 days, 653 visitors
120 95 140 210 88

ওই output-টা সেই input-এর জন্য, যেখানে এক লাইনে 120 95 আর পরের লাইনে 140 210 88। cin >> x space আর নতুন লাইন একইভাবে পার হয়ে যায়, তাই কোথায় লাইন ভাঙল তাতে কিছু যায় আসে না।

Range-for কোনো index ছাড়াই প্রতিটা গোনা যোগ করে। দ্বিতীয় loop-এ index লাগে, কারণ প্রথমটা বাদে প্রতিটা মানের আগে space বসে। i একটা size_t, মানে size() যে type দেয় সেটাই, তাই তুলনায় signed আর unsigned মেশে না।

Run in Compiler
Example 2: প্রথম আর শেষ index, আর ফাঁকা উত্তর

Zara রোজ একটা তাপমাত্রা লেখে। কোন কোন দিন আগের দিনের চেয়ে গরম ছিল? ওই দিনগুলোর index print করো, 0 থেকে গুনে, নয়তো none।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    vector<int> temps(n);
    for (int i = 0; i < n; i++) {
        cin >> temps[i];
    }

    // Day 0 has no day before it, so the walk starts at day 1.
    vector<int> warmer;
    for (int i = 1; i < n; i++) {
        if (temps[i] > temps[i - 1]) {
            warmer.push_back(i);
        }
    }

    if (warmer.empty()) {
        cout << "none\n";
        return 0;
    }
    // int, like the loop above: one index style in one program.
    for (int j = 0; j < (int)warmer.size(); j++) {
        if (j > 0) {
            cout << ' ';
        }
        cout << warmer[j];
    }
    cout << '\n';
    return 0;
}
1 4 5

ওই output-টা 6 আর 30 32 31 31 35 36 input-এর জন্য। দিন 1 দিন 0-কে ছাড়িয়ে যায়, আর দিন 4 আর 5 দুইটাই নিজের আগের দিনকে ছাড়ায়। দিন 3 শুধু সমান হয়।

Loop শুরু হয় 1 থেকে, তাই temps[i - 1] কখনো vector-এর আগে পড়ে না। থামে n - 1-এ, মানে শেষ index-এ, তাই শেষ দিনটাও check হয়। n = 1 হলে loop একবারও চলে না, warmer খালিই থাকে, আর program print করে none। Zara ঠিক এই case-টাই সবার আগে চালায়।

Run in Compiler
Example 3: নতুনরা যে loop লেখে, আর তার খরচ

Kenji ওর coffee stand-এ একটা লাইন সামলায়। প্রত্যেক customer-এর কিছু মিনিট লাগে, আর প্রত্যেকে সামনের সবার জন্য অপেক্ষা করে। সবার মোট অপেক্ষা কত? Kenji সামনের customer-কে serve করে erase করে দেয়, আসল লাইন যেভাবে এগোয়।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    vector<int> minutes(n);
    for (int i = 0; i < n; i++) {
        cin >> minutes[i];
    }

    long long clock = 0;
    long long total_wait = 0;
    while (!minutes.empty()) {
        total_wait += clock;
        clock += minutes.front();
        minutes.erase(minutes.begin());
    }
    cout << total_wait << '\n';
    return 0;
}
24

ওই output-টা 5 আর 3 1 4 1 5 input-এর জন্য। পাঁচজন অপেক্ষা করে 0, 3, 4, 8 আর 9 মিনিট, মিলে 24।

উত্তর ঠিক, সমস্যা হলো erase-টা। প্রতিটা erase(minutes.begin()) তখনো অপেক্ষায় থাকা প্রত্যেককে এক ঘর বাঁয়ে সরায়। উপরের table-এর প্রথম সারি ঠিক এটাই: সামনে থেকে 200000 বার erase, 20503 ms পরে থামিয়ে দেওয়া। সমাধান হলো vector-কে না ছুঁয়ে একবার হেঁটে যাওয়া: for (int m : minutes) { total_wait += clock; clock += m; }। কিছুই erase করতে হয় না, কারণ serve হয়ে যাওয়া customer-দের আর কেউ পড়ে না।

Run in Compiler

এটা কোথায় কাজে লাগে

  • Unix-এর uniq command। পাশাপাশি থাকা একই লাইনের প্রতিটা দল থেকে ও একটা করে রাখে, তাই text-এর লাইনের জন্য sort | uniq মানেই unique-sorted। uniq -c প্রতিটা দলের দৈর্ঘ্যও print করে, যেটা frequency-table-এর কাজ।
  • PostgreSQL-এর window function। SUM(amount) OVER (ORDER BY day) প্রতিটা সারির জন্য একটা running total দেয়। ওই কলামটা একটা prefix sum, prefix-range-sums যে vector-টা একবার ভরে রাখে।
  • LeetCode-এর problem 167, "Two Sum II"। ওটা একটা sorted array দিয়ে এমন দুইটা মান চায়, যাদের যোগফল একটা target। কাঙ্ক্ষিত সমাধান হলো pair-sum-count-এর two-pointer হাঁটা, শুধু গোনাটা ছাড়া।
  • Microsoft Excel-এর AutoSum। একটা table বাছো, সাথে ওর নিচের একটা ফাঁকা সারি আর ডানের একটা ফাঁকা কলাম, তারপর AutoSum চাপো। Excel প্রতিটা কলামের মোট আর প্রতিটা সারির মোট ভরে দেয়, এক click-এ grid-row-col-sums।

যে ভুলগুলো সবাই করে

১. Erase-এর পরেও এগিয়ে যাওয়া।

for (size_t i = 0; i < steps.size(); i++) {
    if (steps[i] < 0) {
        steps.erase(steps.begin() + i);
    }
}

কোনো command line-এই বার্তা নেই। Erase-এর পরে পরের reading-টা সরে এসে index i-তে বসে, আর i++ ওটাকে টপকে যায়। Sample 3 -1 4 -1 -5 9 2-এর জন্য Compiler Explorer-এ GCC 12 print করেছে 3 4 -5 9 2: -5 আরেকটা negative-এর ঠিক পরে ছিল, তাই ওটা check-ই হয়নি। এগোবে শুধু মান রাখলে, নয়তো erase-remove ব্যবহার করো।

২. size_t index দিয়ে উল্টো হাঁটা।

for (size_t i = marks.size() - 1; i >= 0; i--) {

Playground-এর command line (-O2 -std=c++17, কোনো -Wall নেই) কিছু বলে না। -Wall -Wextra দিলে GCC 12 সাবধান করে: warning: comparison of unsigned expression in '>= 0' is always true [-Wtype-limits]। size_t 0-এর নিচে নামলে ঘুরে হয়ে যায় 18446744073709551615, তাই loop কখনো থামে না। Compiler Explorer-এ লাইনটা শুরু হয়েছে 48 91 62 85 70 0 49 0 0 দিয়ে, তারপর vector-এর আগের memory পড়তে পড়তে চলেছে, যতক্ষণ না run-টা থামিয়ে দেওয়া হয়েছে। উল্টো হাঁটো একটা int দিয়ে: for (int i = (int)marks.size() - 1; i >= 0; i--)।

৩. খালি vector-এ pop_back।

} else if (op == "pop") {
    strokes.pop_back();
}

কোনো command line-এই বার্তা নেই, কারণ vector খালি কি না সেটা জানা যায় শুধু চলার সময়। এটা undefined behaviour। Playground-এ খালি vector-এ pop_back() চলেছে সফল হয়ে, আর তারপর size() print করেছে 18446744073709551615। push-pop-print-এর sample-এ Compiler Explorer-এ একবার চালাতে print হয়েছে 5 8, তারপর 5 8 0 0 0 0 98497 আর হাজার হাজার শূন্য, যতক্ষণ না থামিয়ে দেওয়া হয়েছে। আগে strokes.empty() জিজ্ঞেস করো।

৪. যেখানে মোটের জন্য long long লাগে, সেখানে vector<int>।

vector<int> prefix(n + 1, 0);

কোনো command line-এই বার্তা নেই, আর sample পাশ করে, কারণ ওর মোটগুলো ছোট। 1000000000-এর তিনটা দিন আর প্রশ্ন 1 3-এর জন্য Compiler Explorer-এ একবার চালাতে print হয়েছে -1294967296। এটা আসল মোট থেকে 232 বাদ: signed overflow, মানে undefined behaviour। মানগুলো int-এ ধরে, কিন্তু মোট যায় 2 x 1014 পর্যন্ত।

মাথা খাটাও

push-pop-print-এ সর্বোচ্চ 200000টা operation থাকে। তবু statement আরেকটা নিয়ম যোগ করে: সব print মিলিয়ে বড়জোর 100000টা stroke print হবে। Kenji বলে নিয়মটা বাড়াবাড়ি।

নিয়মটা না থাকলে একটা test-এ ঠিক program-কে দিয়ে সর্বোচ্চ কয়টা stroke print করানো যেত? আর তাতে ওই test-এর expected output file-এর কী দশা হতো?

200000 operation-এর এমন একটা script লেখো, যেটা output-কে যতটা সম্ভব লম্বা করে।

Problem 1: reverse-inputসহজফ্রি

একগাদা পরীক্ষার খাতা যে ক্রমে এসেছে, Amara সেই ক্রমে mark-গুলো type করেছে। ও কখনো গোনেনি কয়টা। শেষ খাতা থেকে প্রথম খাতা পর্যন্ত mark print করো। Starter push_back দিয়ে mark-গুলো vector<int> marks-এ পড়ে রাখে।

Input. এক বা একাধিক পূর্ণসংখ্যা, space বা নতুন লাইন দিয়ে আলাদা। শুরুতে কোনো count নেই: input শেষ না হওয়া পর্যন্ত পড়ো।

Output. এক লাইনে mark-গুলো উল্টো ক্রমে, একটা করে space দিয়ে আলাদা।

Constraints. 1 থেকে 200000টা mark, প্রতিটা -1000000000 থেকে 1000000000-এর মধ্যে।

Sample. Input 70 85 62 91 48 দিলে 48 91 62 85 70।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    vector<int> marks;
    int x = 0;
    while (cin >> x) {
        marks.push_back(x);
    }

    // Print the marks from the last one back to the first,
    // on one line, separated by single spaces.

    return 0;
}
Run in Compiler

Hint 1

Mark-গুলো বসে আছে index 0 থেকে marks.size() - 1 পর্যন্ত। শেষে পড়া mark-টা কোন index-এ?

Hint 2

একটা int index-কে (int)marks.size() - 1 থেকে 0 পর্যন্ত নামাও, দুই মাথাসহ। দুইটা mark-এর মাঝে একটা space print করো, শেষটার পরে কখনো না।

Solution

Loop-টা হলো for (int i = (int)marks.size() - 1; i >= 0; i--)। শেষ mark বের হয় সবার আগে, আর marks[0] বের হয় সবার শেষে। Index একটা int, তাই ওটা -1-এ পৌঁছাতে পারে, আর loop index 0-এর পরে থামে। Index 0-এরটা বাদে প্রতিটা mark-এর পরে একটা space বসে। একটা মাত্র mark থাকলে loop একবার চলে।

size_t index কখনো 0-এর নিচে নামে না, তাই i >= 0 সবসময় সত্য, ভুল 2 যেমন দেখায়। marks.size() থেকে শুরু করলে শেষের এক বাক্স পরে পড়া হয়। Hidden test-এ আছে একটা মাত্র mark, অনেক লাইনে ছড়ানো mark, নতুন লাইন ছাড়া শেষ লাইন, আর 200000টা mark। while (cin >> x) এর সবগুলোই সামলায়।

Problem 2: largest-and-positionসহজফ্রি

Kenji n-টা round-এর প্রতিটার score লিখে রেখেছে, কিছু negative। ওর সবচেয়ে ভালো score আর কোন round-এ ও প্রথম ওটা পেয়েছিল, সেটা বের করো, round গোনা হবে 0 থেকে।

Input. প্রথম লাইনে n। দ্বিতীয় লাইনে n-টা score।

Output. এক লাইনে: সবচেয়ে বড় score, একটা space, আর ওটা প্রথম যে index-এ এসেছে।

Constraints. 1 <= n <= 200000, আর প্রতিটা score -1000000000 থেকে 1000000000-এর মধ্যে।

Sample. Input 6 আর 4 9 7 9 2 -3 দিলে 9 1।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    vector<int> scores(n);
    for (int i = 0; i < n; i++) {
        cin >> scores[i];
    }

    // Print the largest score and the index of its first occurrence,
    // counting from 0, separated by a space.

    return 0;
}
Run in Compiler

Hint 1

প্রতিটা score negative হতে পারে। -5 -2 -9 হাতে করে দেখো। সবচেয়ে ভালো score শুরু হবে কোথা থেকে?

Hint 2

সবচেয়ে ভালোটা শুরু করো scores[0] থেকে, আর ওর index 0 থেকে, তারপর হাঁটো index 1 থেকে। দুইটাই বদলাও শুধু তখন, যখন কোনো score সত্যিই বড়।

Solution

সবচেয়ে ভালোটা শুরু হয় scores[0] থেকে, একটা আসল score থেকে, তাই সবচেয়ে ছোট সম্ভাব্য score আন্দাজ করতে হয় না। পরের কোনো score ওটাকে বদলায় শুধু > দিয়ে, তাই সবচেয়ে ভালোটার দ্বিতীয় copy index সরায় না। n = 1 হলে loop একবারও চলে না, আর উত্তর scores[0] আর 0।

0 থেকে শুরু করলে সব score negative হলে print হয় 0 0। >= দিলে index চলে যায় শেষ copy-তে, আর sample-এর জন্য print হয় 9 3। Hidden test-এ সবচেয়ে ভালো score আছে শুরুতে, শেষে আর তিনবার, সাথে সব score সমান আর সব -1000000000-এর test।

Problem 3: drop-negativesমাঝারিPro

Sensor কাজ না করলে Bob-এর step counter একটা negative সংখ্যা লেখে। vector<int> steps থেকে প্রতিটা negative reading সরাও, আর বাকিগুলো ক্রমমতো print করো। 0 আসল reading, ওটা থাকবে।

Input. প্রথম লাইনে n। দ্বিতীয় লাইনে n-টা reading।

Output. এক লাইনে 0 বা তার বেশি reading-গুলো, একটা করে space দিয়ে আলাদা, আর কিছু না থাকলে empty শব্দটা।

Constraints. 1 <= n <= 20000, আর প্রতিটা reading -1000000000 থেকে 1000000000-এর মধ্যে।

Sample. Input 7 আর 3 -1 4 -1 -5 9 2 দিলে 3 4 9 2।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    vector<int> steps(n);
    for (int i = 0; i < n; i++) {
        cin >> steps[i];
    }

    // Remove every negative reading from steps, keeping the order of the rest.
    // Then print what is left on one line, or the word empty if nothing is.

    return 0;
}
Run in Compiler

Hint 1

কাগজে একটা মান erase করো, তারপর যে index-টা খালি হলো সেটা দেখো। এখন ওখানে কোন reading বসে আছে?

Hint 2

হয় index এগোও শুধু কোনো reading রাখলে, নয়তো Amara-র erase-remove ব্যবহার করো। bool is_broken(int reading) লেখো, যেটা reading < 0 return করে, আর ওটা remove_if-কে দাও। steps.empty() হলে empty print করো।

Problem 4: insert-at-positionসহজPro

Alice-এর পড়ার list-এ n-টা বইয়ের নম্বর। ও বই x এমনভাবে যোগ করে, যাতে ওটা হয় k-তম বই, 1 থেকে গুনে। তাই k = 1 মানে সবার আগে, আর k = n + 1 মানে সবার শেষে।

Input. n, তারপর n-টা বইয়ের নম্বর, তারপর k আর x।

Output. Insert-এর পরের n + 1টা নম্বর এক লাইনে, একটা করে space দিয়ে আলাদা।

Constraints. 1 <= n <= 200000, 1 <= k <= n + 1, আর প্রতিটা নম্বর -1000000000 থেকে 1000000000-এর মধ্যে।

Sample. Input 5, 10 20 30 40 50 আর 3 25 দিলে 10 20 25 30 40 50।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    vector<int> books(n);
    for (int i = 0; i < n; i++) {
        cin >> books[i];
    }
    int k = 0;
    int x = 0;
    cin >> k >> x;

    // Insert x so that it becomes the k-th book, counting from 1.
    // Then print all n + 1 books on one line, separated by single spaces.

    return 0;
}
Run in Compiler

Hint 1

Position গোনা হয় 1 থেকে, index গোনা হয় 0 থেকে। Insert-এর পরে x-এর index কত হতে হবে?

Hint 2

books.insert(books.begin() + (k - 1), x); x-কে বসায় index k - 1-এর সামনে। Submit করার আগে কাগজে k = 1 আর k = n + 1 মিলিয়ে দেখো।

Problem 5: frequency-tableসহজফ্রি

Maria n-টা খাতা দেখেছে, প্রতিটার mark 0 থেকে 100। যে mark-গুলো এসেছে, প্রতিটা কতজন পেয়েছে সেটা print করো।

Input. প্রথম লাইনে n। দ্বিতীয় লাইনে n-টা mark।

Output. যে mark এসেছে তার প্রতিটার জন্য এক লাইন, ছোট থেকে বড় ক্রমে: mark, একটা space, ওর count।

Constraints. 1 <= n <= 200000, আর প্রতিটা mark 0 থেকে 100-এর মধ্যে।

Sample. Input 8 আর 3 7 3 0 100 7 3 5 দিলে পাঁচটা লাইন: 0 1, 3 3, 5 1, 7 2 আর 100 1।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    vector<int> marks(n);
    for (int i = 0; i < n; i++) {
        cin >> marks[i];
    }

    // For every mark that appears, from the smallest to the largest,
    // print one line: the mark, a space, and how many students got it.

    return 0;
}
Run in Compiler

Hint 1

Mark চলে 0 থেকে 100। দুই মাথাসহ, এখানে আলাদা mark কয়টা?

Hint 2

vector<int> count(101, 0) বানাও, আর প্রতিটা mark m-এর জন্য count[m]-এ 1 যোগ করো। তারপর m-কে 0 থেকে 100 পর্যন্ত হাঁটাও, আর 0-এর বেশি count-গুলো print করো।

Solution

Mark-টাই index, তাই count[m] গোনে mark m। 101টা বাক্স 0 থেকে 100 পর্যন্ত ঢেকে দেয়, দুই মাথাসহ। বাক্সগুলোর উপর দিয়ে উপরের দিকে হাঁটলে mark-গুলো ছোট থেকে বড় ক্রমেই print হয়, কোনো sort ছাড়া। কোনো count 200000 পেরোয় না, তাই int-ই যথেষ্ট।

count(100)-এ 100-এর জন্য কোনো বাক্স নেই, তাই count[100]++ শেষের পরে লেখে। এটা undefined behaviour, আর GCC 12 কিছু বলে না, কারণ index জানা যায় শুধু চলার সময়। প্রতিটা বাক্স print করলে শূন্যগুলোও print হয়। Hidden test-এ আছে শুধু 0 আর 100, প্রতিটা mark একবার করে, আর একই mark-এর 200000টা copy।

Problem 6: prefix-range-sumsমাঝারিPro

David-এর কাছে n দিনের বিক্রি আছে, আর refund-এর পরে কোনো দিন negative-ও হতে পারে। ও q-টা প্রশ্ন করে: দিন l থেকে দিন r পর্যন্ত মোট বিক্রি কত?

Input. n আর q, তারপর n দিনের বিক্রি, তারপর q-টা লাইন, প্রতিটায় l আর r।

Output. প্রতিটা প্রশ্নের জন্য এক লাইন: দিন l থেকে r পর্যন্ত মোট, দুই মাথাসহ।

Constraints. 1 <= n, q <= 200000, প্রতিটা দিনের বিক্রি -1000000000 থেকে 1000000000-এর মধ্যে, আর 1 <= l <= r <= n, দিনের নম্বর শুরু 1 থেকে।

Sample. Input 5 3, 4 -2 7 1 3, 1 3, 2 5 আর 4 4 দিলে তিন লাইনে 9, 9 আর 1।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    int q = 0;
    cin >> n >> q;
    vector<int> sales(n);
    for (int i = 0; i < n; i++) {
        cin >> sales[i];
    }

    // Anything you want to prepare before the questions goes here.

    for (int k = 0; k < q; k++) {
        int l = 0;
        int r = 0;
        cin >> l >> r;
        // Print the total sales of days l to r, both included.
        // Days are numbered from 1.
    }
    return 0;
}
Run in Compiler

Hint 1

n = q = 200000 আর প্রতিটা প্রশ্ন সব দিন চাইলে কয়টা যোগ লাগে, গুনে দেখো। তারপর উপরের table-টা দেখো।

Hint 2

প্রশ্নগুলোর আগে vector<long long> prefix(n + 1, 0) ভরো prefix[i] = prefix[i - 1] + sales[i - 1] দিয়ে। দিন l থেকে r মানে প্রথম r দিন, প্রথম l - 1 দিন বাদে।

Problem 7: grid-row-col-sumsমাঝারিPro

Kenji-র tournament table একটা grid: প্রত্যেক খেলোয়াড়ের একটা সারি, প্রতিটা round-এর একটা কলাম, আর একটা ঘরে ওই round-এর point, penalty হলে negative। প্রতিটা সারির মোট আর প্রতিটা কলামের মোট print করো।

Input. r আর c, তারপর c-টা করে পূর্ণসংখ্যার r-টা লাইন।

Output. লাইন 1-এ r-টা সারির যোগফল, আর লাইন 2-এ c-টা কলামের যোগফল, একটা করে space দিয়ে আলাদা।

Constraints. 1 <= r, c <= 500, আর প্রতিটা পূর্ণসংখ্যা -1000000000 থেকে 1000000000-এর মধ্যে।

Sample. Input 2 3, 1 2 3 আর 4 5 6 দিলে দুই লাইনে 6 15 আর 5 7 9।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int r = 0;
    int c = 0;
    cin >> r >> c;
    vector<vector<int>> points(r, vector<int>(c));
    for (int i = 0; i < r; i++) {
        for (int j = 0; j < c; j++) {
            cin >> points[i][j];
        }
    }

    // Line 1: the sum of each row, from the first row to the last.
    // Line 2: the sum of each column, from the first column to the last.

    return 0;
}
Run in Compiler

Hint 1

একটা সারির সবচেয়ে বড় সম্ভাব্য যোগফল বের করো। ওটা কি একটা int-এ ধরে?

Hint 2

vector<long long> row_sum(r, 0) আর col_sum(c, 0) বানাও। Grid-এর উপর দিয়ে একবার হাঁটলেই points[i][j] যোগ হয়ে যায় row_sum[i]-এ আর col_sum[j]-এ।

Problem 8: push-pop-printমাঝারিPro

Amara একটা drawing app-এর undo list একটা script দিয়ে test করে। push x stroke x-কে শেষে যোগ করে। pop শেষ stroke সরায়, list খালি থাকলে কিছুই করে না। print list-টা print করে।

Input. n, তারপর n-টা লাইন, প্রতিটায় একটা operation।

Output. প্রতিটা print-এর জন্য এক লাইন: stroke-গুলো একটা করে space দিয়ে আলাদা, নয়তো empty শব্দটা।

Constraints. 1 <= n <= 200000, 1 <= x <= 1000, আর সব print মিলিয়ে বড়জোর 100000টা stroke print হয়।

Sample. Input 7, push 5, push 8, print, pop, pop, pop আর print দিলে দুই লাইনে 5 8 আর empty।

#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    vector<int> strokes;
    for (int i = 0; i < n; i++) {
        string op;
        cin >> op;
        if (op == "push") {
            int x = 0;
            cin >> x;
            // Add x at the end of strokes.
        } else if (op == "pop") {
            // Remove the last stroke, or do nothing if there is none.
        } else {
            // op is "print": print the strokes on one line,
            // or the word empty if there are none.
        }
    }
    return 0;
}
Run in Compiler

Hint 1

Sample দুইবার push-এর পরে তিনবার pop করে। তৃতীয় pop-এর কী করার কথা, আর খালি vector-এ pop_back() কী কথা দেয়?

Hint 2

push মানে push_back(x)। pop pop_back() call করে শুধু !strokes.empty() হলে। print print করে empty, নয়তো stroke-গুলো, মাঝে space দিয়ে।

Problem 9: unique-sortedকঠিনPro

Zara ওর thermometer-এর n-টা reading সাজিয়েছে, আর অনেকগুলো বারবার এসেছে। আলাদা reading কয়টা, আর reading-গুলো নিজেরা, প্রতিটা একবার করে print করো।

Input. n, তারপর ছোট থেকে বড় ক্রমে n-টা পূর্ণসংখ্যা (সমান মান পাশাপাশি থাকতে পারে)।

Output. লাইন 1-এ k, মানে আলাদা reading কয়টা। লাইন 2-এ ওরা ছোট থেকে বড় ক্রমে, একটা করে space দিয়ে আলাদা।

Constraints. 1 <= n <= 200000, আর প্রতিটা reading -1000000000 থেকে 1000000000-এর মধ্যে।

Sample. Input 8 আর 1 1 2 3 3 3 7 7 দিলে দুই লাইনে 4 আর 1 2 3 7।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    cin >> n;
    vector<int> readings(n);
    for (int i = 0; i < n; i++) {
        cin >> readings[i];
    }

    // The readings are in non-decreasing order.
    // Line 1: how many distinct readings there are.
    // Line 2: the distinct readings in order, separated by single spaces.

    return 0;
}
Run in Compiler

Hint 1

Input সাজানো। একটা মানের প্রতিটা copy বাকি copy-গুলোর তুলনায় কোথায় বসে?

Hint 2

একটা reading নতুন, যদি ওটা প্রথমটা হয় বা আগেরটা থেকে আলাদা হয়। নয়তো কাজটা library-কে দাও: readings.erase(unique(readings.begin(), readings.end()), readings.end());, সাথে <algorithm> include করে।

Problem 10: pair-sum-countকঠিনPro

Maria এক সারিতে নম্বর লেখা n-টা card রাখে, কিছু নম্বর negative। দুইটা card জেতে, যদি ওদের নম্বর যোগ করলে ঠিক t হয়। Position-এর এমন জেতা জোড়া কয়টা, গোনো।

Input. n আর t, তারপর n-টা card-এর নম্বর, যেকোনো ক্রমে।

Output. এক লাইনে position-এর এমন জোড়া i < j কয়টা, যাদের নম্বর যোগ করলে t হয়।

Constraints. 1 <= n <= 200000, প্রতিটা নম্বর -1000000000 থেকে 1000000000-এর মধ্যে, আর -2000000000 <= t <= 2000000000।

Sample. Input 6 10 আর 3 7 5 5 2 8 দিলে 3।

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n = 0;
    long long t = 0;
    cin >> n >> t;
    vector<int> cards(n);
    for (int i = 0; i < n; i++) {
        cin >> cards[i];
    }

    // Print how many pairs of positions i < j have
    // cards[i] + cards[j] equal to t.

    return 0;
}
Run in Compiler

Hint 1

n = 200000-এ প্রতিটা জোড়া check করা মানে প্রায় 2 x 1010টা check। আগে card-গুলো sort করো। সবচেয়ে ছোট আর সবচেয়ে বড় card-এর যোগফল তোমাকে কী বলে?

Hint 2

lo রাখো 0-তে আর hi রাখো n - 1-এ। যোগফল t-এর কম হলে lo উঠবে, বেশি হলে hi নামবে। মিলে গেলে দুই মাথায় সমান card-এর দলগুলো গোনো, ওদের গুণফল যোগ করো, আর দুই দলই পেরিয়ে যাও।

সচরাচর যে প্রশ্নগুলো আসে

  • Judge শুধু output দেখে। Starter যে vector-এর নাম বলে, ওটা কি ব্যবহার করতেই হবে?

    Judge টের পায় না, তাই কোনো নিয়ম তোমাকে বাধ্য করে না। largest-and-position তো পড়তে পড়তেই সমাধান করা যায়। কিন্তু এই set vector-এর অনুশীলন, আর hint আর solution-গুলো starter-এর vector নিয়েই কথা বলে। রেখে দেওয়া vector-এর উপর দিয়ে দুইবার হাঁটাও যায়।

  • endl না লিখে '\n' কেন?

    endl একটা নতুন লাইন লেখে, তারপর cout-কে flush করে, মানে ওর buffer তখনই বাইরে পাঠিয়ে দেয়। 200000 লাইন print করা program 200000 বার flush করবে। '\n' শুধু নতুন লাইনটা লেখে, আর buffer বাইরে যায় ভরে গেলে বা program শেষ হলে।

  • আমার program sample পাশ করে। তাহলে hidden test কেন ফেল করে?

    Sample একটাই ছোট উদাহরণ, statement বোঝানোর জন্য বাছা। Hidden test-এ আরও থাকে n = 1, সব মান সমান, সব মান negative, ফাঁকা উত্তর আর সবচেয়ে বড় n। Submit করার আগে Zara এগুলো চালিয়ে দেখে, তুমিও দেখো।

  • Loop-এর index int হবে নাকি size_t?

    size_t size()-এর সাথে মেলে, তাই সামনের দিকের loop-এ একই রকম type-এর তুলনা হয়। উল্টো loop-এ লাগে int, কারণ size_t কখনো 0-এর নিচে নামতে পারে না। এক program-এ একটাই বাছো, আর দরকার হলে size()-কে একবার cast করো।

মূল কথা

  • প্রতিটা starter দুইটা fast লাইনের পরে একটা নাম দেওয়া vector-এ পড়ে; তুমি print করো একটা করে space আর '\n' দিয়ে।
  • 109 পর্যন্ত মান একটা int-এ ধরে; যোগফল, জোড়ার হিসাব আর জোড়ার count-এ লাগে long long।
  • সবচেয়ে বড় input-এ ধাপ গোনো: 200000 মানের loop-এর ভিতরে erase বা যোগফল এক সেকেন্ড পেরিয়ে যায়।
  • Erase-remove, prefix vector, count vector আর unique-erase, প্রতিটাই loop-এর ভিতরের loop-কে এক pass বানায়।
  • Sample-এর আগে n = 1, সব সমান, সব negative আর ফাঁকা উত্তর দিয়ে test করো।
  • আরও গভীরে যেতে চাইলে: CP and Interview Pack, vector-এর pattern আর bug gallery (Pro)।

এরপর আসছে cheat sheet, এক পাতায় পুরো vector, তারপর module test।

lesson ৭ শেষ

সব problem accepted হলেই lesson শেষ।

৩ টা free problem-এর মধ্যে ০ টা accepted

পরেরটা: Cheat sheet: এক পাতায় vector

Problem: vector | Learn C++ STL | Progsity