T1:舞会配对
本题难度中等,注意到数据范围很小,正解极有可能是朴素的搜索枚举方法。
记 \(m = 2n\)
使用回溯法,依次考虑第 \(1 \sim m\) 个人,要与谁配对
记录状态:
- 当前考虑配对的人的编号
- 已经配成的对数
- 当前(未完全的)配对方案的幸福度
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int n;
cin >> n;
int m = 2*n;
vector<vector<int>> a(m, vector<int>(m));
rep(i, m)rep(j, m) cin >> a[i][j];
ll ans = -1e18;
vector<int> matched(m);
auto dfs = [&](auto& f, int step, int cnt, ll now) -> void {
if (cnt == n) {
ans = max(ans, now);
return;
}
if (matched[step]) {
f(f, step+1, cnt, now);
return;
}
for (int i = step+1; i < m; ++i) {
// 1. 剪枝
if (matched[i]) continue;
// 2. 试探
matched[i] = matched[step] = true;
f(f, step+1, cnt+1, now*a[step][i]);
// 3. 回溯
matched[i] = matched[step] = false;
}
};
dfs(dfs, 0, 0, 1);
cout << ans << '\n';
return 0;
}
如何分析时间复杂度?
\(m = 16\) 的极端情形:
对第 \(1\) 个待配对的人,后续有 \(15\) 个可选的与其配对的人
对第 \(2\) 个待配对的人,后续有 \(13\) 个可选的与其配对的人
对第 \(3\) 个待配对的人,后续有 \(11\) 个可选的与其配对的人
\(~\vdots\)
对第 \(8\) 个待配对的人,后续有 \(1\) 个可选的与其配对的人
由乘法原理,需要枚举的所有配对方案为:
关于本题的背景:
本题为边权之积,可以通过取对数,化积为和。
实质为 二分图最大权匹配问题。
时间复杂度为 \(O(n^3)\)。
适用范围:提高组以上
T2:发积分
本题难度较大,显然 \(x\) 越大 \(k\) 天内能发的分数越多。二分 \(x\),\(check(x)\) 计算发 \(k\) 天实际能发多少积分,判断是否能发出 \(n\) 分。找到能发完 \(n\) 分的最小 \(x\)。
本题 \(k\) 比较小,\(check\) 计算时,直接模拟 \(k\) 天发积分即可。
使用整除分块技巧,一次可达 \(O(\sqrt{k})\),所以 \(k\) 的范围可以强化到 \(10^{12}\)。
显然,\(x=n\) 时,可以满足要求:
答案要求 \(x_{\min}\),而 \([x_{\min}, n]\) 中的任何值都满足要求。
考虑到可能的答案分成了两段,可以考虑使用二分答案法。
二分答案 \(=\) 二分框架 \(+\) 判定函数。
判断:对于当前的二分值 \(x\),是否满足:
每次判断时间复杂度 \(O(k)\),二分范围为 \([1, n]\)。
总时间复杂度 \(O(k\log n)\)。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
ll n; int k;
cin >> n >> k;
ll wa = 0, ac = n;
while (ac-wa > 1) {
ll wj = (ac+wa)/2;
auto ok = [&]{
ll res = 0;
for (int i = 1; i <= k; ++i) {
res += (wj+i-1)/i;
}
return res >= n;
}();
(ok ? ac : wa) = wj;
}
cout << ac << '\n';
return 0;
}
浙公网安备 33010602011771号