Tree Matching
أُعطيت شجرة مكوّنة من n عقد.
المطابقة (matching) هي مجموعة من الأحرف بحيث تكون كل عقدة طرفاً في حرف واحد على الأكثر. ما هو أكبر عدد ممكن من الأحرف في مطابقة؟
المدخل:
- السطر الأول: عدد صحيح
n— عدد العقد (1..n). - ثم
n-1سطراً: أحرفa bتصف حرفاً غير موجه في الشجرة.
المخرجات:
- اطبع عدداً صحيحاً واحداً: أكبر حجم لمطابقة.
القيود:
1 ≤ n ≤ 2 · 10^5
مثال:
المدخل:
5
1 2
1 3
3 4
3 5
المخرجات:
2
الشرح: إحدى المطابقات الممكنة هي (1,2) و (3,4).
Solution
نريد أكبر مجموعة من الأحرف بحيث لا يشترك أي حرفين مختارين في طرف (عقدة) واحدة.
على الشجرة، يمكن حل ذلك باستخدام DP بعد تجذير الشجرة. الفكرة المحلية الأساسية هي: هل العقدة “مُستخدمة” أصلاً لأنها متطابقة مع أبيها؟ لأن ذلك يمنعها من التطابق مع أي ابن.
فكرة DP
نجذر الشجرة عند العقدة 1. لكل عقدة v نحسب:
dp0[v]: أكبر حجم مطابقة داخل الشجرة الفرعية لـvعندما لا تكون v متطابقة مع أبيها (أي أنvحرة: يمكن أن تتطابق مع ابن واحد على الأكثر، أو لا تتطابق مع أي ابن)dp1[v]: أكبر حجم مطابقة داخل الشجرة الفرعية لـvعندما تكون v متطابقة مع أبيها (أي أنvمستخدمة مسبقاً، ولا يمكنها التطابق مع أي ابن)
الجواب سيكون dp0[root] لأن الجذر ليس له أب.
العلاقة التكرارية (Recurrence)
لتكن children(v) هي الجيران باستثناء الأب.
1) إذا كانت v متطابقة مع أبيها dp1[v]
عندها لا يمكن لـ v التطابق مع الأبناء، لذلك يجب أن يكون كل ابن c في حالة “غير متطابق مع الأب”:
\(dp1[v] = \sum_{c \in children(v)} dp0[c]\)
2) إذا كانت v غير متطابقة مع أبيها dp0[v]
هناك احتمالان:
vلا تتطابق مع أي ابن عندها كل الأبناء مستقلون في الحالةdp0:
\(base = \sum_{c} dp0[c]\)
vتتطابق مع ابن واحد بالضبطuاختيار الحرف(v,u)يضيف+1، ويجبر الابنuأن يكون “متطابقاً مع أبيه” أي في الحالةdp1[u]. باقي الأبناء يبقون فيdp0.
إذا طابقنا v مع ابن معيّن u فالمجموع:
\(base - dp0[u] + dp1[u] + 1\)
نختار أفضل u أو نختار عدم التطابق:
\(dp0[v] = \max\Big(base,\ \max_{u \in children(v)} (base - dp0[u] + dp1[u] + 1)\Big)\)
برهان SRTBOT
S — الحالة (State)
dp0[v] = أفضل حجم مطابقة داخل الشجرة الفرعية لـ v عندما تكون v حرة بالنسبة لأبيها. dp1[v] = أفضل حجم مطابقة داخل الشجرة الفرعية لـ v عندما تكون v متطابقة مسبقاً مع أبيها.
هذه الحالات تغطي كل الاحتمالات لأن التفاعل الوحيد بين الشجرة الفرعية وباقي الشجرة يكون عبر حرف الأب.
R — العلاقة التكرارية (Recurrence)
- إذا كانت
vمستخدمة بسبب الأبdp1[v]فهي لا يمكن أن تكون طرفاً في أي حرف مطابقة آخر → لا يمكنها التطابق مع الأبناء → مجموعdp0للأبناء. - إذا كانت
vحرةdp0[v]فيمكنها التطابق مع ابن واحد على الأكثر. إما لا تطابق أحداًbaseأو تطابق ابناً واحداًu، فنحوّل حالة ذلك الابن فقط إلىdp1[u]ونضيف حرفاً واحداً.
إذن الصيغ السابقة ضرورية وكافية.
T — الانتقال (Transition)
نحسب الأبناء أولاً عبر DFS، ثم:
base = sum(dp0[child])dp1[v] = basedp0[v] = max(base, max(base - dp0[u] + dp1[u] + 1))
B — الحالات الأساسية (Base cases)
لعقدة ورقية v:
- لا يوجد أبناء → المجاميع تساوي 0
dp1[v] = 0(لا يمكن التطابق مع الأبناء)dp0[v] = 0(لا يوجد ابن لتطابقه)
صحيح.
O — ترتيب الحساب (Order)
DFS بترتيب ما بعد الزيارة (Post-order): نحسب DP للأبناء قبل الأب. كل عقدة تستخدم فقط قيم DP للأبناء، لذا التبعيات محققة.
T — التعقيد الزمني والذاكرة (Time/Space)
كل حرف يُعالج عدداً ثابتاً من المرات.
- الزمن:
O(n) - الذاكرة:
O(n)لقائمة المجاورات + DP + الاستدعاء التراجعي.
الجواب النهائي
اطبع dp0[1] بعد حساب DP من الجذر 1.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int>> g;
vector<long long> dp0, dp1;
void dfs(int v, int p) {
long long base = 0;
// First: solve children
for (int to : g[v]) {
if (to == p) continue;
dfs(to, v);
base += dp0[to];
}
// If v is matched with parent -> cannot match any child
dp1[v] = base;
// If v is not matched with parent -> either match with no child, or with exactly one child
dp0[v] = base;
for (int u : g[v]) {
if (u == p) continue;
long long candidate = base - dp0[u] + dp1[u] + 1; // match (v,u)
dp0[v] = max(dp0[v], candidate);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
g.assign(n + 1, {});
for (int i = 0; i < n - 1; i++) {
int a, b;
cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
}
dp0.assign(n + 1, 0);
dp1.assign(n + 1, 0);
int root = 1;
dfs(root, -1);
cout << dp0[root] << "\n";
return 0;
}