复健
复健!
P.S. 因为前期本来复健了一点,但是现在才想起来我是不是得记录一下,所以前面的就当是回归手感了。
势必改掉乱开 \(LL\) 的坏习惯。
9.22
早上十点半来图书馆复健,做题如下。
T1 P1563 [NOIP 2016 提高组] 玩具谜题
直接异或判断方向,喜提 90 pts,下载到了一个超级大样例,看不出来是啥,但是感觉是在 0 1 反复横跳的问题。
查询题解发现没有判断 0 和 n,改完 A。
贴码
code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m, np = 1;
struct node
{
int dir;
string name;
} a[N];
inline int check(int num) { return (num % n + n) % n; }
int main()
{
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; ++ i) cin >> a[i].dir >> a[i].name;
while(m -- )
{
int ddir, d;
scanf("%d %d", &ddir, &d);
if(ddir ^ a[np].dir) np = check(np + d);
else np = check(np - d);
np = !np ? n : np;
}
cout << a[np].name;
return 0;
}
对于 T2 我想做一个违背祖宗的决定,开始猪国杀
祖宗不太允许我违背,今天读题但未动手。咕咕咕
今天结束
9.23
给室友讲了数组相关
9.24
开始时间 23点07分。
今日复健二分。
二分学习笔记,参考 yxc 的笔记
新笔记,升级的二分模板比 yxc 这个要效果更好,可以将两类统一到一个模板下。
二分新模板
算是的一个缺点是要多记录一个量,但是我觉得一换二很划算。
while(l <= r)
{
LL mid = (l + r) >> 1, tmp = check(mid);
if(tmp >= k)
{
l = mid + 1;
if(tmp == k) ansr = mid;
} else r = mid - 1;
}
二分的前提条件,需要满足二分的这一段序列单调
二分是一种折半的思想,通过每次取半来快速逼近目标值。
取中间值 \(mid\) 然后用 \(mid\) 进行验证,如果过大,则缩小有边界,反之,缩小左边界。
T1 P1024 [NOIP 2001 提高组] 一元三次方程求解
本题是对于小数的二分,对于含小数的二分通常会设置一个 \(eps\) 来确定最小二分限度,每次的二分缩小边界也可以直接写为 \(mid\) 的赋值。
code
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-5;
double a, b, c, d;
inline double f(double n) { return a * n * n * n + b * n * n + c * n + d; }
int main()
{
cin >> a >> b >> c >> d;
for(double i = -100; i < 100; i += 1)
{
if(!f(i)) printf("%.2lf ", i);
else if(f(i) * f(i + 1) < 0)
{
double l = i, r = i + 1;
while(r - l > eps)
{
double mid = (l + r) / 2;
if(f(mid) * f(i) < 0) r = mid;
else l = mid;
}
printf("%.2lf ", l);
}
}
return 0;
}
T2 P2678 [NOIP 2015 提高组] 跳石头
分析可知,本题求解最小值的最大值,同时可以知道,我们可以通过模拟来快速查验每一个可能的距离值,因此考虑二分。
题目中数据范围为 int 不需要开 long lnog 。
对于本题的 \(mid\) 如果不符合则说明跳跃距离仍然过大可以缩小,所以范围开到 \(r = mid - 1\) (此处边界在过程中验证了不可),反之则 \(l = mid\)。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
int n, m, len;
int a[N];
inline bool check(int lm)
{
int cnt = 0, pos = 0;
for(int i = 1; i <= n; ++ i)
{
if(a[i] - pos < lm) ++ cnt;
else pos = a[i];
}
return cnt > m;
}
int main()
{
cin >> len >> n >> m;
for(int i = 1; i <= n; ++ i) cin >> a[i];
a[ ++ n] = len;
int l = 0, r = len;
while(l <= r)
{
int mid = (l + r) >> 1;
if(check(mid)) r = mid - 1;
else l = mid + 1;
}
cout << r;
return 0;
}
今日到此为止,截止时间 00点30分。
9.26
继续练手二分题单吧,顺带两道前缀和的题目,今天给高中朋友讲了前缀和的知识。
突然想到有一个题目完美结合了二者((
但是夜深,且跑步恢复,遂早睡,交给周末的我解决。
10.9
真是一个酣畅淋漓的周末啊(((
题目是借教室
本题采取笔记中的第二个策略,查询最大的符合的单子编号,然后 $ + 1 $ 获得边界。
新板子,没那么多选择,干就完了。
题目中比较坑的一点是注意到每一个单子中最大的借用量是 \(10^{9}\) 那么当我们订单数量多于两个的时候,会出现爆掉 int 的情况,就得开 long long。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
typedef long long LL;
LL n, m, a[N];
LL d[N], st[N], fi[N], diff[N];
inline bool check(int x)
{
memset(diff, 0, sizeof(diff));
for(int i=1; i <= x; ++ i)
{
diff[st[i]] += d[i];
diff[fi[i] + 1] -= d[i];
}
for(int i=1; i <= n + 1; ++ i)
{
diff[i] += diff[i - 1];
if(diff[i] > a[i]) return false;
}
return true;
}
int main()
{
scanf("%lld %lld", &n, &m);
for(int i=1; i <= n; ++ i) scanf("%lld", &a[i]);
for(int i=1; i <= m; ++ i) scanf("%lld %lld %lld", &d[i], &st[i], &fi[i]);
int l = 0, r = m;
while(l <= r)
{
int mid = (l + r) >> 1;
if(check(mid)) l = mid + 1;
else r = mid - 1;
}
// while(l < r)
// {
// int mid = (l + r + 1) >> 1;
// if(check(mid)) l = mid;
// else r = mid - 1;
// }
if(l < m) printf("-1\n%d", l);
else printf("0");
return 0;
}
10.10
T1 质检员
昨天开的这个题目,昨天把代码大框架写下来了,但是时间不早就先睡了(有舍友歇息了)。今天补档。
质检员发现要多次查询区间,并且查询数目不大且操作单一,直接考虑前缀和提前维护。并且整体呈现有单调关系,不难推出,随着 \(W\) 的增大 \(y\) 会逐渐减小,满足序列单调即考虑二分。但是注意 \(W\) 与 \(y\) 呈现负相关,所以在 \(check\) 函数里面需要注意最后判断的条件。
多测要清空,多测要清空,多测要清空,多测要清空,多测要清空
重要的事情说五遍,有一个 \(y\) 没清空,导致最后卡了半天。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
typedef long long LL;
int n, m;
int w[N], v[N], st[N], fi[N];
LL y, sumv[N], sumn[N], ans, s;
inline void calc(int W)
{
for(int i=1; i <= n; ++ i)
{
sumv[i] = (w[i] >= W) * v[i];
sumn[i] = (w[i] >= W);
}
for(int i=1; i <= n; ++ i) sumv[i] += sumv[i - 1], sumn[i] += sumn[i - 1];
}
inline LL check(int x)
{
y = 0;
memset(sumv, 0, sizeof(sumv));
memset(sumn, 0, sizeof(sumn));
calc(x);
for(int i=1; i <= m; ++ i)
y += (sumn[fi[i]] - sumn[st[i] - 1]) * (sumv[fi[i]] - sumv[st[i] - 1]);
// ans = min(ans, abs(y - s));
// if(y > s) return false;
// else return true;
return y;
}
int main()
{
scanf("%d %d %lld", &n, &m, &s);
for(int i=1; i <= n; ++ i) scanf("%d %d", &w[i], &v[i]);
for(int i=1; i <= m; ++ i) scanf("%d %d", &st[i], &fi[i]);
int l = 0, r = 1e6;
ans = 1e18;
while(l <= r)
{
int mid = (l + r) >> 1;
LL x = check(mid);
if(x <= s) r = mid - 1;
else l = mid + 1;
ans = min (ans, abs(x - s));
}
// while(l < r)
// {
// int mid = (l + r) >> 1;
// if(check(mid)) r = mid;
// else l = mid + 1;
// }
printf("%lld", ans);
return 0;
}
T2 自动刷题机
本题要求一个二分对,一个最小值的最大值,一个最大值的最小值,可以说很是综合。
在修正我的代码的时候,阅读发现了一个新版二分写法,多记录一个状态然后可以一个代码实现求取上面两个值。比较板子直接放代码了。
值得注意的一点是本题要开 long long,一个代码行数为 \(\pm 10^{9}\),总共有 \(10^5\) 个计算,极限数据为 \(10^14\),所以需要开 long long。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int n, k, a[N];
LL ansl, ansr;
inline LL check(LL x)
{
LL cnt = 0, res = 0;
for(int i=1; i <= n; ++ i)
{
res = max(res + a[i], 0ll);
if(res >= x) cnt ++, res = 0;
}
return cnt;
}
int main()
{
scanf("%d %d", &n, &k);
for(int i=1; i <= n; ++ i) scanf("%d", &a[i]);
LL l = 1, r = 1e18;
while(l <= r)
{
LL mid = (l + r) >> 1;
LL tmp = check(mid);
if(tmp <= k)
{
r = mid - 1;
if(tmp == k) ansl = mid;
} else l = mid + 1;
}
l = 1, r = 1e18;
while(l <= r)
{
LL mid = (l + r) >> 1, tmp = check(mid);
if(tmp >= k)
{
l = mid + 1;
if(tmp == k) ansr = mid;
} else r = mid - 1;
}
if(!ansl) printf("-1");
else printf("%lld %lld", ansl, ansr);
return 0;
}
10.11
对前面写了的二分题目进行了代码更行。采用了新的二分计算方法。
今日复健分治。
明白一个函数的作用并相信它能完成这个任务,千万不要跳进这个函数里面企图探究更多细节, 否则就会陷入无穷的细节无法自拔,人脑能压几个栈啊。
快速幂 就是一个比较典型的分治问题。通过将幂次化为二进制,来将时间复杂度从 \(O(b)\) 降低到 \(O(\log{b})\)。
code
#include <bits/stdc++.h>
using namespace std;
int a, b, p;
inline int qmi(int a, int b, int p)
{
int res = 1;
while(b)
{
if(b & 1) res = res * 1ll * a % p;
a = a * 1ll * a %p;
b >>= 1;
}
return res;
}
int main()
{
scanf("%d %d %d", &a, &b, &p);
printf("%d^%d mod %d=%d", a, b, p, qmi(a, b, p));
return 0;
}
被劝退,复习点别的(((
整点差分和前缀和
10.14
做了一个前缀和的题目练了下手,直接开始搜索,狠狠弥补一下我的薄弱 dfs。