复健

复健!

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。

posted @ 2025-09-25 00:34  carp_oier  阅读(21)  评论(0)    收藏  举报