題目連結
題解
知識點:貪心,博弈論。
一個 \(01\) 串中 \(01, 10\) 的個數差只與首尾兩個字元相關,若首尾字元相同,則個數差為 \(0\) ,否則為 \(1\) 或 \(-1\) 。因此,樹上除了根節點和葉子節點的 \(?\) 是不影響葉子節點權值的(但可能影響策略,導致答案不一樣),我們只需要考慮葉子節點和根節點的情況即可。
設 \(cnt_0, cnt_1, cnt_2\) 分別為值為 \(0, 1, ?\) 的葉子節點個數。
如果根節點不是 \(?\) ,那麼答案顯然為與根節點值相反的葉子節點數,加上 \(\lceil cnt_2 / 2 \rceil\) 。接下來考慮根節點是 \(?\) 的情況。
當 \(cnt_0 \neq cnt_1\) 時,不妨設 \(cnt_0 < cnt_1\) ,先手有如下幾種策略:
- 先手第一步確定根節點的值為 \(0\) ,此時答案為 \(cnt_1 + \lfloor cnt_2 / 2 \rfloor\) 。
- 先手第一步確定根節點的值為 \(1\) ,此時答案為 \(cnt_0 + \lfloor cnt_2 / 2 \rfloor\) 。
- 先手第一步確定一個葉子節點為 \(0\) ,那麼後手可以確定根節點為 \(1\) ,此時答案為 \(cnt_0 + \lfloor cnt_2 / 2 \rfloor + 1\) 。
- 先手第一步確定一個葉子節點為 \(1\) ,那麼後手可以確定根節點為 \(1\) ,此時答案為 \(cnt_0 + \lfloor cnt_2 / 2 \rfloor\) 。
顯然第一種策略答案最優。
當 \(cnt_0 = cnt_1\) 時,有如下幾種情況:
- 不存在非根節點和葉子節點的 \(?\) ,無論如何答案都為 \(cnt_0 + \lfloor cnt_2 / 2 \rfloor\) 。
- 存在一個非根節點和葉子節點的 \(?\) ,先手可以選擇一個非根節點和葉子節點的 \(?\) 隨意賦值,後手無論如何賦值,答案都為 \(cnt_0 + \lceil cnt_2 / 2 \rceil\) 。
- 存在多個非根節點和葉子節點的 \(?\) ,如果數量為偶數,則與不存在的情況等價,否則與存在一個的情況等價。
時間複雜度 \(O(n)\)
空間複雜度 \(O(n)\)
程式碼
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int deg[100007];
bool solve() {
int n;
cin >> n;
for (int i = 1;i <= n;i++) deg[i] = 0;
for (int i = 2;i <= n;i++) {
int u, v;
cin >> u >> v;
deg[u]++;
deg[v]++;
}
string s;
cin >> s;
s = "?" + s;
int cnt[3] = {};
for (int i = 2;i <= n;i++) if (deg[i] == 1) cnt[s[i] == '?' ? 2 : s[i] == '1']++;
if (s[1] != '?') cout << cnt[s[1] == '0'] + (cnt[2] + 1) / 2 << '\n';
else {
if (cnt[0] == cnt[1]) {
int delta = count(s.begin() + 1, s.end(), '?') - cnt[2] - 1;
cout << cnt[0] + (cnt[2] + (delta & 1)) / 2 << '\n';
}
else cout << max(cnt[0], cnt[1]) + cnt[2] / 2 << '\n';
}
return true;
}
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}