Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
2891 字
14 分钟
个人训练赛20241211

个人训练赛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分数规划

读完题意就可以转化为尽量减少lrb\frac{\sum \sqrt{\left| l - r\right |}}{\sum b}

下面介绍一下01分数规划问题:给定两个都包含nn个正整数的正数数列{a1,a2,a3,...,ana_1,a_2,a_3,...,a_n}和{b1,b2,b3,...,bnb_1,b_2,b_3,...,b_n},同时选出k个a和b,求maxi=1naisii=1nbisi\frac{\sum_{i=1}^{n}a_i s_i }{\sum_{i=1}^{n}b_i s_i },其中si=1s_i=1si=0s_i=0表示选或不选第ii个数,且i=1nsi=k\sum_{i=1}^{n} s_i=k

为了加快速度,我们可以用“猜”的方法,猜一个数xx,使

  • i=1naisii=1nbisix\frac{\sum_{i=1}^{n}a_i s_i }{\sum_{i=1}^{n}b_i s_i } \ge x

移项得f=i=1n(aixbi)kf = \sum_{i=1}^{n}(a_i - x b_i)\ge k

01分数规划有两种解决办法:二分法,Dinkelbach算法。

下面利用二分来找到零点:

我们需要最小化一个分式。经典的方法是将分式的最小化问题转化为参数λ\lambda的函数,然后通过二分法寻找最优的λ\lambda

将原问题转化:

具体来说我们的目标是最小化:

总挫败感所用休息点的风景值总和=lrjbj\frac{\text{总挫败感}}{\text{所用休息点的风景值总和}} = \frac{\sum \sqrt{|l - r_j|}}{\sum b_j}

  1. 设目标函数为:

λ=lrjbj\lambda = \frac{\sum \sqrt{|l - r_j|}}{\sum b_j}

  1. 为了最小化这个分式,我们可以令:

lrjλbj=0\sum \sqrt{|l - r_j|} - \lambda \sum b_j = 0

  1. 我们可以看成是对于给定的λ\lambda,判断是否存在一条路径,使得:

lrjλbj\sum \sqrt{|l - r_j|} \leq \lambda \sum b_j

  1. 如果存在这样的路径,我们就尝试更小的λ\lambda;如果不存在,则尝试更大的λ\lambda

使用二分法寻找最优的λ\lambda

  1. 由于λ\lambda是连续的,我们可以在一个范围内对λ\lambda进行二分搜索,直到找到最小的λ\lambda

我们可以将休息点看成图中的节点,两个休息点之间的边权为:

边权=l(xixj)λbi\text{边权} = \sqrt{|l - (x_i - x_j)|} - \lambda b_i

其中,xix_i 是休息点的坐标,bib_i是休息点的风景值。

我们需要找到从起点到终点的最小代价路径,使得总代价0\leq 0。如果存在这样的路径,那么对于当前的 λ\lambda,答案就是可行的。

一些注意点:

实际上思路正确的话,除了二分应该不太会有导致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

最后这道题的时间复杂度为O(n2log(lrj))O(n^{2}log(\sum\sqrt{|l - r_j|}))

std:

#include <bits/stdc++.h>
using namespace std;
#define int long long
using 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:构造 数学 数论

可以涉及的算法:裴蜀定理 二元线性丢番图方程

问题描述

给定一个整数序列x1,x2,,xnx_1, x_2, \dots, x_n,可以进行以下操作任意次:

  • 选择任意两个整数 xxyy,计算2xy2x - y,并将结果视为新的整数。

现在,给定一个目标值kk,问是否可以通过上述操作,从序列中的某个元素出发,经过若干次操作,得到kk

为了方便分析,我们先对操作进行等价变换,并理解其本质。

操作等价变换 原操作:2xy2x - y

可以看作:

  1. 2xy=x+xy 2x - y = x + x - y 这样,我们可以将操作视为:从一个元素xx开始,加上任意两个数的差xyx - y

  2. 由于xyx - y可以为正数或负数,因此,操作的本质是:从某个元素开始,加上任意数对的差。

考虑序列元素之间的差,我们定义序列:

  • ai=xixi1a_i = x_i - x_{i-1},对于i=2,3,,ni = 2,3,\dots,n 这样,我们可以表示任意两个元素之间的差:

  • xixj=k=j+1iakx_i - x_j = \sum_{k = j+1}^{i} a_k,当 i>ji > j 因此,任意的xix_i 都可以表示为:

  • xi=x1+k=2iakx_i = x_1 + \sum_{k = 2}^{i} a_k

于是问题转化为:从某个xix_i 出发,通过加上若干个差xpxqx_p - x_q(即a a 的线性组合),得到目标值kk

由于差 xpxqx_p - x_q可以表示为 aa 的线性组合,且系数为整数(可能为负数),因此,我们可以表示目标值与xix_i 之间的差为: kxi=z1a1+z2a2++zn1an1k - x_i = z_1 a_1 + z_2 a_2 + \dots + z_{n-1} a_{n-1}其中,zjz_j为整数。

那么就可以利用裴蜀定理:对于整数序列a1,a2,,an1a_1, a_2, \dots, a_{n-1},存在整数解z1,z2,,zn1z_1, z_2, \dots, z_{n-1},使得线性组合等于某个值的充要条件是,该值是这些数的最大公约数gg的倍数。

因此,我们的问题转化为: 是否存在序列中的某个元素xix_i,使得kxik - x_ig g的倍数。

注意到,所有的xix_i都满足:xix1(modg)x_i \equiv x_1 \pmod{g}

因为xix1=k=2iakx_i - x_1 = \sum_{k=2}^{i} a_k,所以xix1x_i - x_1gg的倍数。

因此,只需检查kk是否与x1x_1同余于模gg意义下,即: 如果(kx1)modg=0(k - x_1) \mod g = 0,总体时间复杂度O(nlog(max(x[i])))O(nlog(max(x[i])))

另外通过丢番图方程入手也可以解决这道题目,手算前几项的递推式可以发现是线性丢番图方程,殊途同归。

科普一个没啥用的小知识:

std::__gcd和std::gcd实现并不同,std::__gcd是euclid算法,std::gcd是stein算法,一般来说,随机数据后者效率高,但实际上没人卡这个就是了。

std:

#include <bits/stdc++.h>
using namespace std;
#define int long long
using 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:计算几何 贪心 排序

题意:略

因为坐标每个象限都是等效的,所以我们可以把所有的坐标全都移到同一个想先,为了方便起见,全部都移到第一象限,保证坐标全部为正数,方便计算。

引用官方题解的一张图方便解释:

codeforces

我们知道,在三角形中任意两边的长度大于第三遍的长度,在此假设有四个点A,B,C,DA,B,C,D存在,有两种挖矿的方法

  1. BBCC DDAA 那么花费的能量为BC+AD|BC|+|AD|
  2. BBAA DDCC 那么花费的能量为AB+CD=AO+DO+BO+CO>BC+AD|AB|+|CD|=|AO|+|DO|+|BO|+|CO|>|BC|+|AD|

显然如果想要花费的能量最小,就是让任意两条线段之间除了定点都不相交

可以将所有的点在xxyy轴上排序,累加所有的线段就可以得到最小值,时间复杂度为O(nlogn)O(nlogn)

std:

#include <bits/stdc++.h>
using namespace std;
#define int long long
using 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:双指针

题意:略

首先,我们需要计算在给定磁盘大小下,数组中最多可以有多少个不同的值。

每个值需要的位数为 k=log2Kk = \lceil \log_2 K \rceil,总共需要的位数为 n×kn \times k。磁盘大小为 II 字节,即 8I8I 位。

因此,需要满足:n×k8In \times k \leq 8I,即:k8Ink \leq \dfrac{8I}{n},由于 kk 为非负整数,我们取其下界。

然后,不同值的最大数量 KK 满足:K2kK \leq 2^k,由于 k=log2Kk = \lceil \log_2 K \rceil,所以:K28InK \leq 2^{\frac{8I}{n}}

但是,KK 最多为 nn,因为数组长度为 nn

为了避免 KK 过大(当 nn 很小时,28In2^{\frac{8I}{n}} 可能很大),我们可以设定一个上限,比如 Kmax=nK_{\text{max}} = n

我们可以对数组进行预处理:排序+统计数组中不同元素的值及其出现次数, 进而滑动窗口寻找最优区间

我们的目标是:

  • 选择一个包含不超过 KK 个不同值的子区间(这些值是连续的,因为经过排序);
  • 使得在这个区间内的元素数量尽可能多,从而被修改的元素数量尽可能少;

具体来讲:

  • 设定一个滑动窗口,初始化左端点和右端点;
  • 枚举所有可能的左右端点组合,使得窗口内的不同值数量不超过 KK
  • 计算每种情况下需要修改的元素数量,即总元素数减去窗口内的元素数量;
  • 取需要修改元素数量最小的方案;

总时间复杂度为 O(nlogn)O(n \log n)

std:

#include <bits/stdc++.h>
using namespace std;
#define int long long
using 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();
}
个人训练赛20241211
https://mizuki.mysqil.com/posts/个人训练赛20241211/
作者
LANSGANBS
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00