ডিজিট ডিপি (Digit DP)
ধরুন আপনি দেখতে চাইছেন যে ১ থেকে ১০,০০০ এর মধ্যে ঠিক কতগুলো লকার নম্বরে কোনো ডিজিট বা অঙ্কের পুনরাবৃত্তি নেই (no repeated digits)। এখন একে একে প্রতিটি নম্বর বা সংখ্যা পরীক্ষা করার বা চেক করার অর্থ হলো ঠিক ১০,০০০ বার তুলনা বা কম্পারিজন (comparisons) করা। কিন্তু যদি N এর মান \(10^{18}\) এর সমান হতো? তবে প্রতি ন্যানোসেকেন্ডে একটি করে নম্বর যাচাই বা চেক করলেও সম্পূর্ণ কাজটি শেষ করতে ৩১ বছর সময় লেগে যেত।
ডিজিট ডিপি (Digit DP) এই সমস্যার সমাধান করে মূলত একটি একটি করে ডিজিট (digit by digit) ক্যান্ডিডেট নম্বর বা সংখ্যা তৈরি করার মাধ্যমে, যা সবচেয়ে উল্লেখযোগ্য বা মোস্ট সিগনিফিক্যান্ট (most significant) ডিজিট বা অঙ্ক থেকে শুরু হয়ে নিচের দিকে যেতে থাকে বা এগোতে থাকে। প্রতিটি পজিশনে বা অবস্থানে এসে, এখন পর্যন্ত থাকা নম্বরটি বা তৈরি হওয়া সংখ্যাটি কোনো ভ্যালিড বা সঠিক উত্তরের দিকে এগোচ্ছে কি না সেটি বোঝার জন্য আমরা শুধু ঠিক ততটুকু স্টেট (state) বা অবস্থাই ট্র্যাক করে বা নজরে রাখি যতটুকু আমাদের না রাখলেই নয় — আর তারপর আমরা সেই ফলাফলগুলোকে বা রেজাল্টগুলোকে ক্যাশ (cache) করে রাখি বা জমা করে ফেলি।
আঁটোসাঁটো বাধা বা নিয়মের সীমাবদ্ধতা (The Tight Constraint)
এর পেছনের মূল ধারণাটি হলো। ধরুন আপনি ০ বা 0 থেকে N = 743 পর্যন্ত থাকা ভ্যালিড বা সঠিক নম্বরগুলো গণনা করতে বা গুনতে চাইছেন। আপনি তখন একবারে একটি করে ডিজিট বা অঙ্ক ব্যবহার করে নম্বরগুলো তৈরি করতে শুরু করেন:
- প্রথম ডিজিট বা অঙ্ক: এটি 0–7 পর্যন্ত যেকোনো কিছু হতে পারে। যদি আপনি 6 কে বা ৭ এর চেয়ে ছোট কোনো অঙ্ককে বেছে নেন, তবে আপনি এখন মুক্ত বা ফ্রি (free) — বাকি ডিজিট বা অঙ্কগুলো 0-9 এর মধ্যে যেকোনো কিছুই হতে পারে। আর যদি আপনি 7 কে বা N এর ঠিক সমান হওয়া ডিজিটকেই বেছে নেন, তখন আপনি একটি আঁটোসাঁটো বা টাইট সীমাবদ্ধতায় (tight constraint) পেঁচিয়ে বা আটকে থাকবেন — যার ফলে এর পরের ডিজিট বা অঙ্ক হতে পারবে বড়জোর 4 পর্যন্ত।
এই টাইট ফ্ল্যাগ (tight flag) বা সীমাবদ্ধতার নির্দেশিকাটি ট্র্যাক করে বা নজর রাখে যে এখন পর্যন্ত বসানো প্রতিটি ডিজিট বা অঙ্ক একদম হুবহু N এর প্রফিক্স বা প্রিফিক্সের (prefix) সাথে মিলে গিয়েছে কি না বা ম্যাচ করেছে কি না। তবে যেকোনো ডিজিট বা অঙ্কের ক্ষেত্রে একবার যদি সেটি N এর চেয়ে নিচের দিকে চলে যায় বা ডাউনে যায়, তখন সাথে সাথেই এই টাইট (tight) নিয়মটি মিথ্যে বা ফলস (false) হয়ে যায় এবং অবশিষ্ট অংশটুকু তখন খুব সহজেই বা স্বাধীনভাবে পুরনো নিয়মে পূরণ করে ফেলা যায়।
স্ট্যান্ডার্ড বা প্রমিত স্টেট ভ্যারিয়েবলগুলো (Standard State Variables)
pos (position) — আমরা বর্তমানে কোন ডিজিট বা অঙ্কের পজিশন বা অবস্থানটি পূরণ করছি (যেখানে 0 = সবচেয়ে বামে থাকা অঙ্ক)।
tight (টাইট বা সীমাবদ্ধতা) — আমরা কি এখনও N-এর প্রিফিক্সের (prefix) দ্বারা সীমাবদ্ধ বা কনস্ট্রেইন্ড (constrained)? যদি এটি সত্য (true) হয়, তবে বর্তমান ডিজিটটি সেই পজিশন বা অবস্থানে শুধুমাত্র N-এর ডিজিট বা অঙ্ক পর্যন্তই যেতে পারবে।
leading_zero (আগাম শূন্য) — আমরা কি ইতিমধ্যেই শুন্য ছাড়া বা নন-জিরো (non-zero) কোনো ডিজিট বা অঙ্ক বসিয়ে ফেলেছি? এটি মূলত "007" কে এমন কিছুর সাথে গণনা করা থেকে আটকায় যার একটি আগাম শূন্য থাকে যা আবার আমাদের পরীক্ষা করা বা নজর রাখা বৈশিষ্ট্যটিকেই (property) ভেঙে ফেলে বা নষ্ট করে দেয়।
accumulated state বা অ্যাকুম্যুলেটেড স্টেট (পুঞ্জীভূত অবস্থা) — আমরা যে বৈশিষ্ট্যটিকেই পজিশনে ধরে রাখছি বা ট্র্যাক করছি: ডিজিটের যোগফল বা ডিজিট সাম মড k (digit sum mod k), বসানো শেষ ডিজিট বা অঙ্ক (যেনো পাশাপাশি থাকা একই সংখ্যা বা অ্যাডজাসেন্ট ডুপ্লিকেট (adjacent duplicate) ধরা যায়), কোনো নির্দিষ্ট ডিজিট বা অঙ্কের গণনা বা কাউন্ট (count), ইত্যাদি।
টেমপ্লেট (The Template)
এর রিকার্সিভ ফাংশনটিকে (recursive function) দেখতে এমন মনে হয়: solve(pos, tight, leading_zero, ...state)। প্রতিটি ধাপে, ডিজিটের বা অঙ্কের লিমিট বা সীমা (tight ? N[pos] : 9) নির্ধারণ করুন বা ঠিক করুন, 0 থেকে শুরু করে লিমিট পর্যন্ত প্রতিটি ডিজিটের চেষ্টা করুন, এর স্টেট বা অবস্থানকে আপডেট (update) করুন, এবং সবশেষে এটিকে রিকার্স বা পুনরাবৃত্তি করুন (recurse)। যেসব স্টেটে বা অবস্থায় tight = false সেগুলোর বা তার ফলাফলগুলোকে মেমোইজ বা ক্যাশে সেভ (memoize) করে রাখুন — এগুলো যেকোনো ধরনের আলাদা প্রিফিক্স (prefixes) জুড়ে পুনরায় ব্যবহারযোগ্য (reusable)।
রেঞ্জ বা সীমানার কুয়েরিগুলো (Range Queries): [L, R] এর মধ্যে গণনা
কোনো ডিজিট ডিপি (Digit DP) খুব স্বাভাবিকভাবেই এই প্রশ্নটির উত্তর দেয় "[0, N] এর মধ্যে ঠিক কতগুলো ভ্যালিড বা সঠিক নম্বর বা সংখ্যা আছে?" [L, R] রেঞ্জ বা সীমার ক্ষেত্রে, সাধারণত count(R) − count(L − 1) পদ্ধতিটি ব্যবহার করা হয়। এটি মাত্র দুটি কলেই (Two calls), এর কাজ সম্পন্ন বা শেষ করে দেয়।
[1, N] এর মধ্যে এমন সংখ্যাগুলো গণনা করুন যেখানে পাশাপাশি কোনো দুটি ডিজিট বা অঙ্ক সমান নয়
সাধারণ কিছু অ্যাকুম্যুলেটেড বা পুঞ্জীভূত স্টেটসমূহ (Common Accumulated States)
- 4 ডিজিট বা অঙ্কটি নেই এমন অবস্থা (No digit 4): এর জন্য কোনো অতিরিক্ত স্টেট বা অবস্থার প্রয়োজন নেই — শুধু লুপে (loop) d == 4 আসলে সেটিকে স্কিপ বা বাদ দিয়ে যান।
- ডিজিটের বা অঙ্কের যোগফল k দ্বারা বিভাজ্য হওয়া (Digit sum divisible by k): এক্ষেত্রে মূলত
sum mod kকে অ্যাকুম্যুলেট বা জমা করুন (যার মান হবে 0 থেকে k−1 পর্যন্ত)। - পাশাপাশি কোনো সমান ডিজিট বা অঙ্ক নেই (No adjacent equal digits): সবশেষে বসানো ডিজিট বা অঙ্কটিকে স্টোর বা সেভ করে রাখুন (যাদের মান হবে 0-9 পর্যন্ত অথবা লিডিং জিরো বা আগাম শূন্যের ক্ষেত্রে −1)।
- একটি নির্দিষ্ট ডিজিট বা অঙ্কের কাউন্ট বা গণনা (Count of a specific digit): সেই ডিজিট বা অঙ্কটি ঠিক কতবার এসেছে সেটি স্টোর বা সেভ করে রাখুন।
এখানকার মোট স্টেটের বা অবস্থার স্পেস (state space) হলো পজিশন বা অবস্থান × 2 (tight) × অতিরিক্ত বা এক্সট্রা (extra) স্টেটসমূহ। N-এর মান \(10^{18}\) পর্যন্ত হলে, এদের পজিশনের মান বা অবস্থানের মান ≤ 19 হবে। এই ধরনের বেশিরভাগ সমস্যার ক্ষেত্রেই খুব ছোট বা অতি ক্ষুদ্র আকারের অতিরিক্ত স্টেট (extra state) থাকে, তাই এর মেমোইজেশন টেবিলে (memoization table) শুধুমাত্র কয়েকশ এন্ট্রি বা মান থাকে।