个人训练赛20241211
E. Hiking
Date:Aug.31, 2019 | Rating:2522 | Number of accepted:66(1.53%) | Number of attempts:185(4.30%) | Total:4303
Tag:二分 动态规划 数论和线性代数
可以涉及的算法:二分 DP 01分数规划
读完题意就可以转化为尽量减少
下面介绍一下01分数规划问题:给定两个都包含个正整数的正数数列{}和{},同时选出k个a和b,求max,其中或表示选或不选第个数,且。
为了加快速度,我们可以用“猜”的方法,猜一个数,使
移项得
01分数规划有两种解决办法:二分法,Dinkelbach算法。
下面利用二分来找到零点:
我们需要最小化一个分式。经典的方法是将分式的最小化问题转化为参数的函数,然后通过二分法寻找最优的。
将原问题转化:
具体来说我们的目标是最小化:
- 设目标函数为:
- 为了最小化这个分式,我们可以令:
- 我们可以看成是对于给定的,判断是否存在一条路径,使得:
- 如果存在这样的路径,我们就尝试更小的;如果不存在,则尝试更大的。
使用二分法寻找最优的:
- 由于是连续的,我们可以在一个范围内对进行二分搜索,直到找到最小的。
我们可以将休息点看成图中的节点,两个休息点之间的边权为:
其中, 是休息点的坐标,是休息点的风景值。
我们需要找到从起点到终点的最小代价路径,使得总代价。如果存在这样的路径,那么对于当前的 ,答案就是可行的。
一些注意点:
实际上思路正确的话,除了二分应该不太会有导致WA的点
- 二分的判断调节不是hi == lo,因为hi和lo是double,所以要设定一个极小值来进行比较,不然直接比较是有误差的 不妨可以试一试下面的代码输出0还是1
#include <bits/stdc++.h>using namespace std;int main() { double a = 0.1, b = 0.2, c = 0.3; cout << (a + b == c) << endl;}
实际上保留20位小数,a,b,c分别为0.10000000000000000555,0.20000000000000001110,0.29999999999999998890
最后这道题的时间复杂度为
std:
#include <bits/stdc++.h>using namespace std;#define int long longusing i64 = long long;const int N = 2.01e6;const int M = 1.01e3;const int mod = 998244353;const double eps = 1e-7;#define endl '\n'
#ifdef LOCAL#include "C:/Users/70510/Desktop/Others/algo/debug.h"#else#define debug(...) 42#endif
int n;double l;double x[N], b[N];double d[N];int pre[N];
bool check(double mid) { for (int i = 0; i <= n; i++) { d[i] = 1e30; } d[0] = 0; pre[0] = -1; for (int i = 1; i <= n; i++) { for (int j = 0; j < i; j++) { double frustration = sqrt(abs(l - (x[i] - x[j]))); double cost = d[j] + frustration - mid * b[i]; if (d[i] > cost) { d[i] = cost; pre[i] = j; } } } return d[n] <= 0;}
void solve() { cin >> n >> l; for (int i = 1; i <= n; i++) { cin >> x[i] >> b[i]; } x[0] = 0; b[0] = 0; double lo = 0, hi = 1e6; while (hi - lo > eps) { double mid = (lo + hi) / 2; if (check(mid)) { hi = mid; } else { lo = mid; } } check(hi); vector<int> ans; for (int i = n; i != -1; i = pre[i]) { if (i != 0) { ans.push_back(i); } } reverse(ans.begin(), ans.end()); for (int i = 0; i < (int)ans.size(); i++) { cout << ans[i] << " \n"[i == (int)ans.size() - 1]; }}
signed main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); int T = 1; // cin >> T; while (T--) solve();}
01分数规划相关题目:Dropping tests
D. Nezzar and Board
Date:Jan.28, 2021 | Rating:1972 | Number of accepted:1244(7.68%) | Number of attempts:1672(10.33%) | Total:16192
Tag:构造 数学 数论
可以涉及的算法:裴蜀定理 二元线性丢番图方程
问题描述
给定一个整数序列,可以进行以下操作任意次:
- 选择任意两个整数 和 ,计算,并将结果视为新的整数。
现在,给定一个目标值,问是否可以通过上述操作,从序列中的某个元素出发,经过若干次操作,得到。
为了方便分析,我们先对操作进行等价变换,并理解其本质。
操作等价变换 原操作:
可以看作:
-
这样,我们可以将操作视为:从一个元素开始,加上任意两个数的差。
-
由于可以为正数或负数,因此,操作的本质是:从某个元素开始,加上任意数对的差。
考虑序列元素之间的差,我们定义序列:
-
,对于 这样,我们可以表示任意两个元素之间的差:
-
,当 因此,任意的 都可以表示为:
-
于是问题转化为:从某个出发,通过加上若干个差(即 的线性组合),得到目标值。
由于差 可以表示为 的线性组合,且系数为整数(可能为负数),因此,我们可以表示目标值与之间的差为: 其中,为整数。
那么就可以利用裴蜀定理:对于整数序列,存在整数解,使得线性组合等于某个值的充要条件是,该值是这些数的最大公约数的倍数。
因此,我们的问题转化为: 是否存在序列中的某个元素,使得 是的倍数。
注意到,所有的都满足:
因为,所以 是的倍数。
因此,只需检查是否与同余于模意义下,即: 如果,总体时间复杂度。
另外通过丢番图方程入手也可以解决这道题目,手算前几项的递推式可以发现是线性丢番图方程,殊途同归。
科普一个没啥用的小知识:
std::__gcd和std::gcd实现并不同,std::__gcd是euclid算法,std::gcd是stein算法,一般来说,随机数据后者效率高,但实际上没人卡这个就是了。
std:
#include <bits/stdc++.h>using namespace std;#define int long longusing i64 = long long;const int N = 2.01e6;const int M = 1.01e3;const int mod = 998244353;#define endl '\n'
#ifdef LOCAL#include "C:/Users/70510/Desktop/Others/algo/debug.h"#else#define debug(...) 42#endif
void solve() { int n, k; cin >> n >> k; vector<int> x(n); for (int i = 0; i < n; i++) { cin >> x[i]; } int g = 0; for (int i = 1; i < n; i++) { g = __gcd(g, abs(x[i] - x[i - 1])); } int delta = abs(k - x[0]); if (delta % g == 0) { cout << "YES" << endl; } else { cout << "NO" << endl; }}
signed main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); int T = 1; cin >> T; while (T--) solve();}
裴蜀定理:【模板】裴蜀定理
裴蜀定理:D. Fox And Jumping
裴蜀定理:Pagodas
裴蜀定理:Function Run Fun
拓展欧几里得算法与二元丢番图方程的解:P1516 青蛙的约会
C. Diamond Miner
Date:March.10, 2021 | Rating:1176 | Number of accepted:5878(53.86%) | Number of attempts:6593 (60.41%) | Total:10914
Tag:计算几何 贪心 排序
题意:略
因为坐标每个象限都是等效的,所以我们可以把所有的坐标全都移到同一个想先,为了方便起见,全部都移到第一象限,保证坐标全部为正数,方便计算。
引用官方题解的一张图方便解释:
我们知道,在三角形中任意两边的长度大于第三遍的长度,在此假设有四个点存在,有两种挖矿的方法
- 挖 挖 那么花费的能量为
- 挖 挖 那么花费的能量为
显然如果想要花费的能量最小,就是让任意两条线段之间除了定点都不相交
可以将所有的点在轴轴上排序,累加所有的线段就可以得到最小值,时间复杂度为
std:
#include <bits/stdc++.h>using namespace std;#define int long longusing i64 = long long;const int N = 2.01e6;const int M = 1.01e3;const int mod = 998244353;#define endl '\n'
#ifdef LOCAL#include "C:/Users/70510/Desktop/Others/algo/debug.h"#else#define debug(...) 42#endif
int x[N], y[N];
void solve() { int n; cin >> n; vector<double> xx; vector<double> yy; int totalPoints = n * 2; for (int i = 0; i < totalPoints; ++i) { int x, y; cin >> x >> y; if (x == 0) { yy.push_back(abs(y)); } else if (y == 0) { xx.push_back(abs(x)); } } sort(yy.begin(), yy.end()); sort(xx.begin(), xx.end()); double ans = 0.0; for (int i = 0; i < n; ++i) { double dis = sqrt(yy[i] * yy[i] + xx[i] * xx[i]); ans += dis; } cout << fixed << setprecision(15) << ans << endl;}
signed main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); int T = 1; cin >> T; while (T--) solve();}
C. MP3
Date:July 30, 2019 | Rating:1748 | Number of accepted:1884(26.85%) | Number of attempts:4402(62.74%) | Total:7016
Tag:双指针
题意:略
首先,我们需要计算在给定磁盘大小下,数组中最多可以有多少个不同的值。
每个值需要的位数为 ,总共需要的位数为 。磁盘大小为 字节,即 位。
因此,需要满足:,即:,由于 为非负整数,我们取其下界。
然后,不同值的最大数量 满足:,由于 ,所以:
但是, 最多为 ,因为数组长度为 。
为了避免 过大(当 很小时, 可能很大),我们可以设定一个上限,比如 。
我们可以对数组进行预处理:排序+统计数组中不同元素的值及其出现次数, 进而滑动窗口寻找最优区间
我们的目标是:
- 选择一个包含不超过 个不同值的子区间(这些值是连续的,因为经过排序);
- 使得在这个区间内的元素数量尽可能多,从而被修改的元素数量尽可能少;
具体来讲:
- 设定一个滑动窗口,初始化左端点和右端点;
- 枚举所有可能的左右端点组合,使得窗口内的不同值数量不超过 ;
- 计算每种情况下需要修改的元素数量,即总元素数减去窗口内的元素数量;
- 取需要修改元素数量最小的方案;
总时间复杂度为 。
std:
#include <bits/stdc++.h>using namespace std;#define int long longusing i64 = long long;const int N = 2.01e6;const int M = 1.01e3;const int mod = 998244353;#define endl '\n'
#ifdef LOCAL#include "C:/Users/70510/Desktop/Others/algo/debug.h"#else#define debug(...) 42#endif
void solve() { int n, I; cin >> n >> I; vector<int> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; } sort(a.begin(), a.end()); vector<int> val; vector<int> count; vector<int> pre(1, 0); int cnt = 1; for (int i = 1; i <= n; ++i) { if (i == n || a[i] != a[i - 1]) { val.push_back(a[i - 1]); count.push_back(cnt); pre.push_back(pre.back() + cnt); cnt = 1; } else { cnt++; } } int max_bits = (8 * I) / n; int K; if (max_bits >= 20) { K = val.size(); } else { K = min((int)val.size(), 1LL << max_bits); } int cht = n; int tot_val = val.size(); for (int lo = 0, hi = 0; hi < tot_val; hi++) { while (hi - lo + 1 > K) { lo++; } int unchanged = pre[hi + 1] - pre[lo]; int changes = n - unchanged; cht = min(cht, changes); } cout << cht << endl;}
signed main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); int T = 1; // cin >> T; while (T--) solve();}