首页 > 其他分享 >L5-NOIP模拟11 测试题解

L5-NOIP模拟11 测试题解

时间:2023-02-13 08:45:34浏览次数:66  
标签:11 le NOIP int sum tr cin L5 include

经典老题

排名 - L5-NOIP模拟11 - 码创未来

A. [CQOI2009] 中位数

洛谷 P1627 | 码创未来

题意

给出 \(1,2,\dots,n\) 的一个排列,统计该排列有多少个长度为奇数的连续子串的中位数是 \(b\)。

对于 \(100\%\) 的数据,满足 \(n\le100000,1\le b\le n\)。

思路

首先在输入的时候记录一下 \(b\) 在排列中的位置 \(pos\)。

然后发现要找的子串一定是包含 \(b\) 的,所以从 \(pos\) 分别往左右找。

对于一个满足条件的子串,大于 \(b\) 的数的个数一定等于小于 \(b\) 的数的个数。

在分别往左右找的过程中,设当前比 \(b\) 大的数的个数为 \(cnt1\),比 \(b\) 小的数的个数为 \(cnt2\)。

那么要找的子串一定满足:左边的 \(cnt1-cnt2\) 一定等于右边的 \(cnt2-cnt1\)。

于是在左边时用数组 \(a\) 记录一下 \(cnt1-cnt2\) 的位置数量 \(a[cnt1-cnt2]\),查找完清空 \(cnt1\) 和 \(cnt2\),在右边时答案加上每一位的 \(a[cnt2-cnt1]\) 即可(乘法原理?)。

数组下标可能有负数,但绝对值不超过 \(n/2\),于是令下标右移 \(n_{\max}/2=50000\)。

非常的暴力。

代码

#include <cstdio>
#include <iostream>
#define f(x, y, z) for (int x = (y); x <= (z); ++x)
#define g(x, y, z) for (int x = (y); x >= (z); --x)
#define FILENAME "median"
using namespace std;
int const N = 1e5 + 10;
int const BASE = 5e4;
int n, a[N], b, ans, pos, delta[N];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    // freopen(FILENAME".in", "r", stdin);
    // freopen(FILENAME".out", "w", stdout);
    
    cin >> n >> b;
    f(t, 1, n) {
        cin >> a[t];
        if (a[t] == b) pos = t;
    }
    int cnt1 = 0, cnt2 = 0;
    delta[BASE] = 1;
    g(i, pos - 1, 1) {
        if (a[i] < a[pos]) ++cnt1;
        else ++cnt2;
        ++delta[cnt1 - cnt2 + BASE];
    }
    cnt1 = cnt2 = 0;
    f(i, pos + 1, n) {
        if (a[i] < a[pos]) ++cnt1;
        else ++cnt2;
        ans += delta[cnt2 - cnt1 + BASE];
    }
    cout << ans + delta[BASE] << '\n';
    
    return 0;
}

B. [HNOI2004] 敲砖块(朴素 DP)

洛谷 P1437 | 码创未来

题意

在一个凹槽中放置了 \(n\) 层砖块,最上面的一层有 \(n\) 块砖,从上到下每层依次减少一块砖。每块砖都有一个分值,敲掉这块砖就能得到相应的分值,如下图所示:

如果你想敲掉第 \(i\) 层的第 \(j\) 块砖的话,若 \(i=1\),你可以直接敲掉它;若 \(i>1\),则你必须先敲掉第 \(i-1\) 层的第 \(j\) 和第 \(j+1\) 块砖。

你现在可以敲掉最多 \(m\) 块砖,求得分最多能有多少。

对于 \(100\%\) 的数据,满足 \(1\leq n\leq 50\),\(1\leq m\leq \dfrac{n\times(n+1)}{2}\)。

思路

设 \(f(j,i,k)\) 表示将第 \(i\) 行第 \(j\) 列的砖敲掉,且当前一共已经敲掉 \(k\) 块砖的最大得分。

根据题意,要想敲掉第 \(j\) 列第 \(i\) 行砖,该列的第 \(1\sim i-1\) 行的砖一定都已经被敲掉了。

而第 \(j+1\) 列则 至少 敲掉了 \(i-1\) 行砖(至多敲掉了 \(n-j\) 行因为一共就这么多)。

现在我们想办法将第 \(j+1\) 列的状态转移到第 \(j\) 列。

枚举第 \(j\) 行敲的砖数 \(i\),然后枚举第 \(j+1\) 行敲的砖数 \(t\),于是写出了方程:

\[f(j,i,k)=\max_{i-1\le t\le n-j}\left\{f(j+1,t,k-i)\right\}+\sum_{l=1}^ia[l,j]. \]

时间复杂度 \(O(n^3m)\),空间复杂度 \(O(n^2m)\),可以通过。

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define g(x, y, z) for (int x = (y); (x) >= (z); --(x))
#define FILENAME "brike"
using namespace std;
int const N = 55, M = 1255;
int const INF = 0x3f3f3f3f;
int n, m, a[N][N], f[N][N][M];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    // freopen(FILENAME".in", "r", stdin);
    // freopen(FILENAME".out", "w", stdout);
    
    cin >> n >> m;
    f(i, 1, n) f(j, 1, n - i + 1) cin >> a[i][j];
    memset(f, 0xc0, sizeof f);
    f[n + 1][0][0] = 0;
    g(j, n, 1) {
        int sum = 0;
        f(i, 0, n - j + 1) {
            sum += a[i][j];
            f(k, i, m) {
                int &tmp = f[j][i][k];
                f(t, i ? i - 1 : 0, n - j)
                    tmp = max(tmp, f[j + 1][t][k - i] + sum);
            }
        }
    }
    int ans = -INF;
    f(i, 1, n) f(j, 1, n - i + 1) ans = max(ans, f[i][j][m]);
    cout << ans << '\n';
    
    return 0;
}

C. 最小密度路径(魔改 Floyd)

洛谷 P1730 | 码创未来

题意

给出一张有 \(N\) 个点 \(M\) 条边的加权有向无环图,接下来有 \(Q\) 个询问,每个询问包括 \(2\) 个节点 \(X\) 和 \(Y\),要求算出从 \(X\) 到 \(Y\) 的一条路径,使得密度最小(密度的定义为,路径上边的权值和除以边的数量)。

\(1 \le N \le 50\),\(1 \le M \le 1000\),\(1\le W \le 10^5\),\(1 \le Q \le 10^5\)。

思路

首先发现 \(Q\) 很可能大于总有序点对数 \(N\times(N-1)\),所以直接预处理出所有点对的答案。

如果边数一定,那么问题就变成了纯纯的最短路问题。于是尝试枚举边数。

设 \(d(i,j,l)\) 表示从 \(i\) 到 \(j\) 经过边数为 \(l\) 的路径的最小边权和。

也就是说,我们在 Floyd 算法中的最短路上又附加了一维信息——边数。

枚举中间点 \(k\),枚举 \(i\) 和 \(j\),然后枚举从 \(i\) 到 \(j\) 的边数 \(l\),并且枚举从 \(i\) 到 \(k\) 的边数 \(t\)(那么从 \(k\) 到 \(j\) 的边数为 \(l-t\)),那么转移方程为

\[f(i,j,l)=\min\{f(i,k,t)+f(k,j,l-t)\}. \]

然而我们发现时间复杂度为 \(O(N^3M^2)\),无法通过。考虑重复决策出现在哪里。

对于这样一条路径:\(i\to\dots\to k\to\dots\to b\to j\),假设用路径 \((i,k)\) 和路径 \((k,j)\) 可以更新路径 \((i,j)\) 的答案,那么路径 \((i,b)\) 和路径 \((b,j)\) 同样也可以更新。并且由于我们枚举了所有的中间点 \(k\),那么一定会枚举到 \(k=b\)。于是只需要令 \(t=1\) 来更新就可以了(事实上令 \(t\) 等于什么都行,不过要保证合法)。即

\[f(i,j,l)=\min\{f(i,k,1)+f(k,j,l-1)\}. \]

于是复杂度就神奇地优化成了 \(O(N^3M)\)。

答案:\(\min\limits_{l\le n-1}\left\{\dfrac{f(x,y,l)}l\right\}\)。

初值:对于不合法的状态(即 \(i\) 无法到达 \(j\)),我们令数组初值全部为 \(+\infty\) 即可。对于每一条权值为 \(w\) 的有向边 \((s,t)\),令初值 \(f(s,t,1)=w\)。

代码

#include <cstdio>
#include <iomanip>
#include <cstring>
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define FILENAME "path"
using namespace std;
typedef double db;
int const N = 55, M = 1010;
int const INF = 0x3f3f3f3f;
int n, m, q, d[N][N][M];
db ans;

inline void getmin(int &p, int const &q) { p = min(p, q); }

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    // freopen(FILENAME".in", "r", stdin);
    // freopen(FILENAME".out", "w", stdout);
    
    cin >> n >> m;
    memset(d, 0x3f, sizeof d);
    f(i, 1, m) {
        int a, b, w;
        cin >> a >> b >> w;
        getmin(d[a][b][1], w);
    }
    f(l, 2, m) f(k, 1, n) f(i, 1, n) f(j, 1, n) {
        int &t = d[i][j][l];
        int val = d[i][k][1] + d[k][j][l - 1];
        getmin(t, val);
    }
    cin >> q;
    cout << fixed << setprecision(3);
    while (q--) {
        int x, y;
        cin >> x >> y;
        ans = INF;
        f(l, 1, n - 1) {
            if (d[x][y][l] == INF) continue;
            ans = min(ans, d[x][y][l] * 1.0 / l);
        }
        if (ans == INF) cout << "OMG!\n";
        else cout << ans << '\n';
    }
    
    return 0;
}

D. [SCOI2006]动态最值(线段树 + 树状数组 / 魔改线段树)

洛谷 P6011 | 码创未来

题意

有一个包含 \(n\) 个元素的数组,要求实现以下操作:

  • DELETE k:删除位置 \(k\) 上的数。右边的数往左移一个位置(即动态的数组下标)。

  • QUERY i j:查询位置 \(i\sim j\) 上所有数的最小值和最大值。

对于 \(100\%\) 的数据,\(1 \le n, m \le {10}^6\),数组中的元素绝对值均不超过 \({10}^9\)。

思路及代码

线段树 + 树状数组

(考试做法)

设删去一些数后的数组下标为 \(val\)。

每删去一个 \(val_i\) 位置上的数,就在树状数组中对应的 \(i\) 位置上加一,这样查询某个 \(i\) 在树状数组中的前缀和 \(sum(i)\) 就能知道它前面删除了几个数,于是也就能获取到对应的 \(val_i=i-sum(i)\)。

但是,题目中给出的是 \(val_i\),那么怎么求出对应的 \(i\) 呢?我们可以对 \(i\) 进行二分,找出第一个满足 \(i-sum(i)=val_i\) 的就是答案。

时间复杂度 \(O(n\times\log n\times\log n)\),洛谷得分 100pts,码创得分 80pts。

代码:

#include <cstdio>
#include <iostream>
#define f(x, y, z) for (int x = (y); x <= (z); ++x)
#define FILENAME "minmax"
using namespace std;
typedef pair<int, int> pii;
int const N = 1e6 + 10;
int const INF = 0x3f3f3f3f;
int n, m, a[N];

struct BIT {
#define lowbit(x) (x & (-x))
    ... //省略
} del;

struct SegTree {
#define lson (u << 1)
#define rson (u << 1 | 1)
    struct Node {
        int l, r, minn, maxx;
    } tr[N << 2];
    inline void pushup(int u) {
        tr[u].minn = min(tr[lson].minn, tr[rson].minn);
        tr[u].maxx = max(tr[lson].maxx, tr[rson].maxx);
        return;
    }
    void build(int u, int l, int r) {
        tr[u].l = l, tr[u].r = r;
        if (l == r) {
            tr[u].minn = tr[u].maxx = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(lson, l, mid);
        build(rson, mid + 1, r);
        pushup(u);
        return;
    }
    void change(int u, int x, int mn, int mx) {
        if (tr[u].l == tr[u].r && tr[u].l == x) {
            tr[u].minn = mn;
            tr[u].maxx = mx;
            return;
        }
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (x <= mid) change(lson, x, mn, mx);
        else change(rson, x, mn, mx);
        pushup(u);
        return;
    }
    inline void cmp(pii &p, pii q) {
        p.first = min(p.first, q.first);
        p.second = max(p.second, q.second);
        return;
    }
    pii get_minmax(int u, int l, int r) {
        pii res = { INF, -INF };
        if (l <= tr[u].l && tr[u].r <= r)
            return make_pair(tr[u].minn, tr[u].maxx);
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (l <= mid) cmp(res, get_minmax(lson, l, r));
        if (r > mid) cmp(res, get_minmax(rson, l, r));
        return res;
    }
} tr;

int val(int x) {
    int l = 0, r = n, mid; //注意l的初值为0.因为r可能等于1
    while (l + 1 < r) {
        mid = (l + r) >> 1;
        if (mid - del.sum(mid) < x) l = mid;
        else r = mid;
    }
    return r;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    // freopen(FILENAME ".in", "r", stdin);
    // freopen(FILENAME ".out", "w", stdout);
    
    cin >> n >> m;
    f(i, 1, n) cin >> a[i];
    tr.build(1, 1, n);
    f(i, 1, m) {
        int op, x, y;
        cin >> op;
        if (op == 1) {
            cin >> x;
            x = val(x);
            tr.change(1, x, INF, -INF);
            del.add(x, 1);
        } else if (op == 2) {
            cin >> x >> y;
            x = val(x), y = val(y);
            pii ans = tr.get_minmax(1, x, y);
            cout << ans.first << ' ' << ans.second << '\n';
        }
    }
    
    return 0;
}
魔改线段树

考虑在线段树上直接记录当前区间被删除的情况。记录 \(sum\) 表示当前区间还剩几个数没被删除。

于是我们在查询或删除的时候,直接传入 当前区间第几个没被删除的数

注意下面的 \(x,l,r\) 都表示当前区间第几个没被删除的数,而不是数组下标。

删除 \(x\):如果当前区间的 \(sum_{lson}\ge x\),说明在左儿子,否则在右儿子。

查询区间 \([l,r]\):设当前线段树区间为 \(u\),讨论三种情况:

  • \([l,r]\subseteq[1,sum_{lson}]\),说明在左儿子,查询左儿子中的 \([l,r]\);
  • \([l,r]\subseteq[sum_{lson}+1,sum_{u}]\),说明在右儿子,查询右儿子中的 \([l-sum_{lson},r-sum_{lson}]\);
  • 否则,说明横跨左儿子和右儿子,分别查询左儿子中的 \([l,sum_{lson}]\) 和右儿子中的 \([1,r-sum_{lson}]\)。

时间复杂度 \(O(n\log n)\)。

代码:

#include <cctype>
#include <cstdio>
#include <iostream>
#define f(x, y, z) for (int x = (y); x <= (z); ++x)
#define FILENAME "minmax"
using namespace std;
typedef pair<int, int> pii;
int const N = 1e6 + 10;
int const INF = 0x3f3f3f3f;
int n, m, a[N];

struct SegTree {
    #define lson (u << 1)
    #define rson (u << 1 | 1)
    struct Node {
        int l, r, minn, maxx, sum;
    } tr[N << 2];
    inline void pushup(int u) {
        tr[u].minn = min(tr[lson].minn, tr[rson].minn);
        tr[u].maxx = max(tr[lson].maxx, tr[rson].maxx);
        tr[u].sum = tr[lson].sum + tr[rson].sum;
        return;
    }
    void build(int u, int l, int r) {
        tr[u].l = l ,tr[u].r = r;
        if (l == r) {
            tr[u].minn = tr[u].maxx = a[l];
            tr[u].sum = 1;
            return;
        }
        int mid = (l + r) >> 1;
        build(lson, l, mid);
        build(rson, mid + 1, r);
        pushup(u);
        return;
    }
    void remove(int u, int x) {
        if (tr[u].l == tr[u].r) {
            tr[u].minn = INF, tr[u].maxx = -INF;
            tr[u].sum = 0;
            return;
        }
        int lsum = tr[lson].sum;
        if (x <= lsum) remove(lson, x);
        else remove(rson, x - lsum);
        pushup(u);
        return;
    }
    inline void cmp(pii &p, pii q) {
        p.first = min(p.first, q.first);
        p.second = max(p.second, q.second);
        return;
    }
    pii get_minmax(int u, int l, int r) {
        pii res = {INF, -INF};
        if (l == 1 && r == tr[u].sum)
            return make_pair(tr[u].minn, tr[u].maxx);
        int lsum = tr[lson].sum, rsum = tr[rson].sum;
        if (l <= lsum && r > lsum) cmp(res, get_minmax(lson, l, lsum)), cmp(res, get_minmax(rson, 1, r - lsum));
        else if (l <= lsum && r <= lsum) cmp(res, get_minmax(lson, l, r));
        else if (l > lsum && r <= lsum + rsum) cmp(res, get_minmax(rson, l - lsum, r - lsum));
        return res;
    }
} tr;

signed main() {

    read(n, m); //快读快写模板省略
    f(i, 1, n) read(a[i]);
    tr.build(1, 1, n);
    f(i, 1, m) {
        int op, x, y;
        read(op);
        if (op == 1) {
            read(x);
            tr.remove(1, x);
        } else if (op == 2) {
            read(x, y);
            pii t = tr.get_minmax(1, x, y);
            print(t.first, t.second);
        }
    }
    
    return 0;
}

标签:11,le,NOIP,int,sum,tr,cin,L5,include
From: https://www.cnblogs.com/f2021ljh/p/17115206.html

相关文章

  • 11 Auth认证模块
    Auth认证模块Auth模块是什么Auth模块是Django自带的用户认证模块:在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时需要实现包括用户注册、登录、认证、......
  • 代码随想录算法Day11 | 20. 有效的括号,1047. 删除字符串中的所有相邻重复项,逆波兰表达
     20.有效的括号题目链接: 20.有效的括号-力扣(LeetCode)题目给定一个只包括'(',')','{','}','[',']' 的字符串s,判断字符串是否有效。有效字符串需满足:左括号必须用......
  • 【题解】P3711 仓鼠的数学题
    poly令人晕眩,令人晕眩的poly.思路伯努利数。首先意识到有一个拉插题也是求自然数幂和,所以答案是关于\(n\)的\(k\)次多项式。考虑设出\(S_{n,k}=\sum\limits_......
  • 力扣---1138. 字母板上的路径
    我们从一块字母板上的位置(0,0)出发,该坐标对应的字符为board[0][0]。在本题里,字母板为board=["abcde","fghij","klmno","pqrst","uvwxy","z"],如下所示。我们可......
  • salt2-11
    SaltStack安装基础环境准备基于centos6和centos7的差异,在两个不同的操作系统中安装saltstack也是不一样的。Centos6需要先安装扩展源,然后在进行安装:Master端yuminstall......
  • python优缺点分析11
    学--就如同你即将看到的一样,Python极其容易上手。前面已经提到了,Python有极其简单的语法。​ 免费、开源--Python是FLOSS(自由/开放源码软件)之一。简单地说,你可以自......
  • x210-2023-02-11
    1、secureCRT中文不乱码,而英文有部分出现乱码,譬如下面图中乱码位置正常应该显示的是asm,但是其余英文并没有乱码,会话选项的字体原来设置的是新宋体,字符编码为默认,字符集为GB......
  • CF1167G题解
    CF1167G题解传送门简化题意:数轴上有n个不相交且处于坐标为非负整数的单位正方形,给m个询问点,求出把这个点右侧的数轴逆时针旋转至与左侧相交时的角度。首先,碰撞时只......
  • NOIP2022游记
    NOIP2022游记今年是第二次考NOIP了,去年第一次考的时候没学过什么东西,混了个省二。今年以高中生的身份考,不仅仅是要省一,还得拿个不错的名次,任务不小。考试当天早上校园里......
  • 11-verilog-有限状态机
    有限状态机写RTL的时候,实现一个功能的时候有很多种方法将系统划分为多个状态,状态之间有状态的转移,第一步,第二步,,,,形成有限状态机流水线技术设计,从输入到输出有......