2.10 L5-NOIP模拟11 测试题解

经典老题

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

A. [CQOI2009] 中位数

洛谷 P1627 | 码创未来

题意

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

对于 100% 的数据,满足 n100000,1bn

思路

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

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

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

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

那么要找的子串一定满足:左边的 cnt1cnt2 一定等于右边的 cnt2cnt1

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

数组下标可能有负数,但绝对值不超过 n/2,于是令下标右移 nmax/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,则你必须先敲掉第 i1 层的第 j 和第 j+1 块砖。

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

对于 100% 的数据,满足 1n501mn×(n+1)2

思路

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

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

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

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

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

f(j,i,k)=maxi1tnj{f(j+1,t,ki)}+l=1ia[l,j].

时间复杂度 O(n3m),空间复杂度 O(n2m),可以通过。

代码

#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 个节点 XY,要求算出从 XY 的一条路径,使得密度最小(密度的定义为,路径上边的权值和除以边的数量)。

1N501M10001W1051Q105

思路

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

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

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

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

枚举中间点 k,枚举 ij,然后枚举从 ij 的边数 l,并且枚举从 ik 的边数 t(那么从 kj 的边数为 lt),那么转移方程为

f(i,j,l)=min{f(i,k,t)+f(k,j,lt)}.

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

对于这样一条路径:ikbj,假设用路径 (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,l1)}.

于是复杂度就神奇地优化成了 O(N3M)

答案:minln1{f(x,y,l)l}

初值:对于不合法的状态(即 i 无法到达 j),我们令数组初值全部为 + 即可。对于每一条权值为 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:查询位置 ij 上所有数的最小值和最大值。

对于 100% 的数据,1n,m106,数组中的元素绝对值均不超过 109

思路及代码

线段树 + 树状数组

(考试做法)

设删去一些数后的数组下标为 val

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

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

时间复杂度 O(nlog2n),洛谷得分 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:如果当前区间的 sumlsonx,说明在左儿子,否则在右儿子。

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

  • [l,r][1,sumlson],说明在左儿子,查询左儿子中的 [l,r]
  • [l,r][sumlson+1,sumu],说明在右儿子,查询右儿子中的 [lsumlson,rsumlson]
  • 否则,说明横跨左儿子和右儿子,分别查询左儿子中的 [l,sumlson] 和右儿子中的 [1,rsumlson]

时间复杂度 O(nlogn)

代码:

#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;
}
posted @   f2021ljh  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示