牛客小白月赛15

题目传送门

官方题解传送门

-------------------这次的题目出的不错,有9题是我赛后能做出来的。但是数据太智障了,重配好几次还是有问题。

A .斑羚飞渡

sol:贪心:如果x[i] + y[i] < m,则第i只斑羚一定到不了对岸,所以要尽量多的使用这种斑羚当跳板;如果x[i] + y[i] >= m,则两只这样的斑羚一定有一只可以借助另一只到达对岸;

ps:比赛的时候数据出锅了,重配之后还是有锅,还好比赛的时候没花时间在这题上面。

  • 贪心
    #include "bits/stdc++.h"
    using namespace std;
    vector<int> v1, v2;
    int n, m, ans;
    int main() {
        int x, y;
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &x, &y);
            if (x >= m) {
                ans++;
                continue;
            }
            if (x + y >= m) v1.push_back(y);
            else v2.push_back(x);
        }
        sort(v1.begin(), v1.end());
        sort(v2.begin(), v2.end());
        for (int i = 0; i < v2.size(); i++) {
            if (v2[i] + v1.back() >= m) {
                v1.pop_back();
                ans++;
            }
        }
        printf("%d\n", ans + v1.size() / 2);
        return 0;
    }
    View Code

     

B .诡异的因数

sol:相当暴力。t * n都能过,优化一下就是t * sqrt(n);

  • t * sqrt(n)的暴力
    #include "bits/stdc++.h"
    using namespace std;
    int main() {
        int t, n;
        scanf("%d", &t);
        while (t--) {
            scanf("%d", &n);
            int cnt = 0;
            for (int i = 1; i * i <= n; i++) {
                if (n % i == 0) {
                    cnt++;
                    if (n / i != i) cnt++;
                }
            }
            printf("%d\n", cnt);
        }
        return 0;
    }
    View Code

     

sol:觉得还有更优的打表方法,但是没想到。去代码区看了一下效率最高的代码果然是打表。值得学习;

  • 打表预处理
    #include "bits/stdc++.h"
    using namespace std;
    const int MAXN = 1e4 + 5;
    int cnt[MAXN];
    void init() {
        for (int i = 1; i <= 10000; i++) {
            if (i * i <= 10000) cnt[i * i]++;
            for (int j = i + 1; i * j <= 10000; j++) cnt[i * j] += 2;
        }
    }
    int main() {
        init();
        int t, n;
        scanf("%d", &t);
        while (t--) {
            scanf("%d", &n);
            printf("%d\n", cnt[n]);
        }
        return 0;
    }
    View Code

     

C .表单

sol:用一个set记录已经出现的串,一个cnt记录上一次去重之后出现了几个重复串。

ps:我被这题坑到了,原先我一直用C语言风格的输入输出。但是scanf不方便输入string类型。所以就采用了cin,后来得知这题的整型用cin输入会wa。不知道是什么数据造成的。而且数据里还有空行之前还没说明,而且op还有非1非2的情况。

  • 事先记录
    #include "bits/stdc++.h"
    using namespace std;
    string s;
    set<string> st;
    int main() {
        int n, q, op, cnt = 0;
        scanf("%d%d", &n, &q);
        while (n--) {
            cin >> s;
            if (st.count(s)) cnt++;
            else st.insert(s);
        }
        while (q--) {
            scanf("%d", &op);
            if (op == 1) {
                cin >> s;
                if (st.count(s)) cnt++;
                else st.insert(s);
            } else {
                printf("%d\n", cnt);
                cnt = 0;
            }
        }
        return 0;
    }
    View Code

     

D .分数的运算

sol:加减通分,乘除直接算。减法是加法逆运算,除法是乘法逆运算。写俩自定义函数就完事。

  • 数学
    #include "bits/stdc++.h"
    using namespace std;
    typedef long long LL;
    void add(LL a, LL b, LL c, LL d) {
        LL g = __gcd(b, d);
        LL x = a * d / g + c * b / g, y = b * d / g;
        if (y < 0) x = -x, y = -y;
        if (x == 0) {
            puts("0 0");
            return;
        }
        g = __gcd(x, y);
        printf("%lld %lld\n", x / g, y / g);
    }
    void mul(LL a, LL b, LL c, LL d) {
        LL x = a * c, y = b * d;
        if (x == 0) {
            puts("0 0");
            return;
        }
        LL g = __gcd(x, y);
        printf("%lld %lld\n", x / g, y / g);
    }
    int main() {
        LL a, b, c, d;
        scanf("%lld%lld%lld%lld", &a, &b, &c, &d);
        add(a, b, c, d);
        add(a, b, -c, d);
        mul(a, b, c, d);
        mul(a, b, d, c);
        return 0;
    }
    View Code

     

E .希望

sol:就是一个01背包,但是每个物品的最小费用不明确。要解决这个问题,可以用线段树;

  • 线段树+01背包
    #include "bits/stdc++.h"
    using namespace std;
    typedef long long LL;
    const int MAXN = 1e5 + 5;
    const int INF = 0x3f3f3f3f;
    struct Node {
        int l, r;
        int _min;
    } segTree[MAXN * 4];
    int a[MAXN], b[MAXN], c[MAXN];
    int dp[MAXN];
    void build(int p, int l, int r) {
        segTree[p].l = l;
        segTree[p].r = r;
        segTree[p]._min = INF;
        if (l != r) {
            int mid = l + r >> 1;
            build(p << 1, l, mid);
            build(p << 1 | 1, mid + 1, r);
        }
    }
    void update(int p, int l, int r, int x) {
        if (segTree[p].l >= l && segTree[p].r <= r) {
            segTree[p]._min = min(segTree[p]._min, x);
            return;
        }
        int mid = segTree[p].l + segTree[p].r >> 1;
        if (l <= mid) update(p << 1, l, r, x);
        if (r > mid) update(p << 1 | 1, l, r, x);
    }
    int query(int p, int i) {
        if (segTree[p].l == segTree[p].r) return segTree[p]._min;
        int mid = segTree[p].l + segTree[p].r >> 1;
        if (i <= mid) return min(query(p << 1, i), segTree[p]._min);
        else return min(query(p << 1 | 1, i), segTree[p]._min);
    }
    int main() {
        int n, k, m, l, r, x;
        scanf("%d%d%d", &n, &k, &m);
        for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
        build(1, 1, n);
        for (int i = 1; i <= m; i++) {
            scanf("%d%d%d", &l, &r, &x);
            update(1, l, r, x);
        }
        LL ans = 0, tot = 0;
        for (int i = 1; i <= n; i++) {
            ans += a[i];
            if (a[i] < 0) {
                b[++tot] = -a[i];
                c[tot] = query(1, i);
            }
        }
        for (int i = 1; i <= tot; i++) {
            for (int j = k; j >= c[i]; j--) {
                dp[j] = max(dp[j], dp[j - c[i]] + b[i]);
            }
        }
        printf("%lld\n", ans + dp[k]);
        return 0;
    }
    View Code

    线段树里的_min有点像懒标记,但是不用向下扩展。

sol:线段树的做法是从别人那看来的,看完发现其实我可以用并查集来代替线段树的功能,而且更高效;

  • 并查集+01背包
    #include "bits/stdc++.h"
    using namespace std;
    typedef long long LL;
    const int MAXN = 1e5 + 5;
    const int INF = 0x3f3f3f3f;
    struct Node {
        int l, r, x;
    } p[MAXN];
    int a[MAXN], b[MAXN], c[MAXN];
    int pre[MAXN], _min[MAXN], dp[MAXN];
    bool cmp(Node a, Node b) {
        return a.x < b.x;
    }
    int find(int k) {
        if (pre[k] == k) return k;
        return pre[k] = find(pre[k]);
    }
    int main() {
        int n, k, m;
        scanf("%d%d%d", &n, &k, &m);
        for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
        for (int i = 1; i <= m; i++) scanf("%d%d%d", &p[i].l, &p[i].r, &p[i].x);
        sort(p + 1, p + 1 + m, cmp);
        for (int i = 1; i <= n; i++) pre[i] = i, _min[i] = INF;
        for (int i = 1; i <= m; i++) {
            for (int j = find(p[i].r); j >= p[i].l; j = find(j)) {
                _min[j] = p[i].x;
                pre[j] = find(j - 1);
            }
        }
        LL ans = 0, tot = 0;
        for (int i = 1; i <= n; i++) {
            ans += a[i];
            if (a[i] < 0) {
                b[++tot] = -a[i];
                c[tot] = _min[i];
            }
        }
        for (int i = 1; i <= tot; i++) {
            for (int j = k; j >= c[i]; j--) {
                dp[j] = max(dp[j], dp[j - c[i]] + b[i]);
            }
        }
        printf("%lld\n", ans + dp[k]);
        return 0;
    }
    View Code

    和hdu1698一样,这种题目也做了好几道了,每次都用线段树解一遍,并查集解一遍。01背包就是很基础的东西了。

F .跳跃的旋律

sol:用离线分块,之前没怎么看过分块,算是了解一下吧。代码很好理解

ps:这题的数据真垃圾,一开始说是暴力都能AC。然后出题人重配了数据之后号称卡飞暴力,然后就把正解也卡了。我一样的代码交上去一回儿通过一回儿超时的。而且这题现在的数据不开快读怕是过不去了吧。

  • 分块
    #include "bits/stdc++.h"
    using namespace std;
    const int MAXN = 500010;
    const int MOD = 20180718;
    int a[MAXN], dp[MAXN], ans[MAXN];
    struct Node {
        int x, y, id;
    } q[MAXN];
    bool cmp(Node a, Node b) {
        return a.y < b.y;
    }
    inline int read() {
        int n = 0; char c = getchar();
        while (c < '0' || c > '9') c = getchar();
        while (c >= '0' && c <= '9') {
            n = n * 10 + (c ^ '0');
            c = getchar();
        }
        return n;
    }
    int main() {
        int n = read(), m = read(), tot = 0, x, y;
        for (int i = 1; i <= n; i++) a[i] = read();
        for (int i = 1; i <= m; i++) {
            x = read(), y = read();
            if (1LL * y * y >= n) {
                int tmp = 1, j = x;
                while (j <= n) {
                    tmp = 1LL * tmp * a[j] % MOD;
                    j += y;
                }
                ans[i] = tmp;
            } else {
                ++tot;
                q[tot].x = x, q[tot].y = y;
                q[tot].id = i;
            }
        }
        sort(q + 1, q + 1 + tot, cmp);
        int d = 0;
        for (int i = 1; i <= tot; i++) {
            if (q[i].y == d) {
                ans[q[i].id] = dp[q[i].x];
                continue;
            }
            d = q[i].y;
            for (int j = n; j >= 1; j--) {
                if (j + d > n) dp[j] = a[j];
                else dp[j] = 1LL * a[j] * dp[j + d] % MOD;
            }
            ans[q[i].id] = dp[q[i].x];
        }
        for (int i = 1; i <= m; i++) printf("%d\n", ans[i]);
        return 0;
    }
    View Code

     

G .FTOS的测试

sol:既然没一个人做出来那我就跳过了吧,连出题人都懒得敲的代码(好奇没有标程数据是怎么来的);

H .数据结构

sol:我是用分块,块内排序二分找相同。这是最好想最好敲的方案了;

ps:这题还考细心,x的范围到10W。而且l可能比r大,比赛结束都没发现这个问题。吃个教训长记性吧。这题比赛的时候数据也出锅,结果重判后还是没通过orz

  • 分块
    #include "bits/stdc++.h"
    using namespace std;
    typedef long long LL;
    const LL MOD = 20180623;
    const LL MAXN = 10010;
    vector<LL> v[MAXN];
    int main() {
        LL n, m, k;
        scanf("%lld%lld", &n, &m);
        for (LL i = 1; i <= n; i++) {
            scanf("%lld", &k);
            v[k].push_back(i);
        }
        for (LL i = 0; i <= 10000; i++) sort(v[i].begin(), v[i].end());
        LL l1, r1, l2, r2, x;
        while (m--) {
            scanf("%lld%lld%lld%lld%lld", &l1, &r1, &l2, &r2, &x);
            if (x > 10000) {
                printf("0\n0\n0\n");
                continue;
            }
            if (l1 > r1) swap(l1, r1); if (l2 > r2) swap(l2, r2);
            LL a = upper_bound(v[x].begin(), v[x].end(), r1) - lower_bound(v[x].begin(), v[x].end(), l1);
            LL b = upper_bound(v[x].begin(), v[x].end(), r2) - lower_bound(v[x].begin(), v[x].end(), l2);
            printf("%lld\n%lld\n%lld\n", a % MOD, b % MOD, a * b % MOD);
        }
        return 0;
    }
    View Code

     听说还可以用什么线段树主席树,树状数组套平衡树来做。但是我不会,哪天会了再来敲。

2019/6/21学完主席树来补题

sol:主席树,一开始用朴素的主席树做了一遍,但是感觉有效的只有叶子节点,大量的非叶子节点完全浪费了。然后就把树给稍作休整进行优化了。

  • 朴素主席树
    #include "cstdio"
    #include "algorithm"
    using namespace std;
    const int MAXN = 100010;
    const int MOD = 20180623;
    struct Node {
        int lson, rson;
        int cnt;
    } tree[MAXN * 20];
    int tot, root[MAXN];
    int build(int l, int r) {
        int i = ++tot;
    //  tree[i].cnt = 0;
        if (l != r) {
            int mid = l + r >> 1;
            tree[i].lson = build(l, mid);
            tree[i].rson = build(mid + 1, r);
        }
        return i;
    }
    int add(int pre, int l, int r, int index) {
        int i = ++tot;
        tree[i] = tree[pre];
        if (l == r) {
            tree[i].cnt++;
        } else {
            int mid = l + r >> 1;
            if (index <= mid) tree[i].lson = add(tree[pre].lson, l, mid, index);
            else tree[i].rson = add(tree[pre].rson, mid + 1, r, index);
        }
        return i;
    }
    int query(int i, int l, int r, int index) {
        if (l == r) return tree[i].cnt;
        int mid = l + r >> 1;
        if (index <= mid) return query(tree[i].lson, l, mid, index);
        else return query(tree[i].rson, mid + 1, r, index);
    }
    int main() {
        int n, m, k;
        scanf("%d%d", &n, &m);
        root[0] = build(1, 10000);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &k);
            root[i] = add(root[i - 1], 1, 10000, k);
        }
        int l1, r1, l2, r2, x;
        while (m--) {
            scanf("%d%d%d%d%d", &l1, &r1, &l2, &r2, &x);
            if (x > 10000) {
                printf("0\n0\n0\n");
                continue;
            }
            if (l1 > r1) swap(l1, r1);
            if (l2 > r2) swap(l2, r2);
            int a = query(root[r1], 1, 10000, x) - query(root[l1 - 1], 1, 10000, x);
            int b = query(root[r2], 1, 10000, x) - query(root[l2 - 1], 1, 10000, x);
            int c = 1LL * a * b % MOD;
            printf("%d\n%d\n%d\n", a, b, c);
        }
        return 0;
    }
    View Code

     

  • 优化版主席树
    #include "bits/stdc++.h"
    using namespace std;
    const int MAXN = 100010;
    const int MOD = 20180623;
    struct Node {
        int lson, rson;
        int cnt;
    } tree[MAXN * 20];
    int tot, root[MAXN];
    // 利用二进制决定一个数字是父节点的左儿子还是右儿子
    // 如 9 = 1001 从根节点出发就是右左左,从低位往高位扫,最高位的1忽略; 
    void build(int i) {
        int now = 0;
        while (i != 1) {
            if (i & 1) {
                if (tree[now].rson == 0) tree[now].rson = ++tot;
                now = tree[now].rson;
            } else {
                if (tree[now].lson == 0) tree[now].lson = ++tot;
                now = tree[now].lson;
            }
            i >>= 1;
        }
    }
    int add(int pre, int index) {
        int i = ++tot;
        tree[i] = tree[pre];
        if (index == 1) {
            tree[i].cnt++;
        } else {
            if (index & 1) tree[i].rson = add(tree[pre].rson, index >> 1);
            else tree[i].lson = add(tree[pre].lson, index >> 1);
        }
        return i;
    }
    int query(int i, int index) {
        if (index == 1) return tree[i].cnt;
        if (index & 1) return query(tree[i].rson, index >> 1);
        else return query(tree[i].lson, index >> 1);
    }
    int main() {
        int n, m, k;
        scanf("%d%d", &n, &m);
    //  root[0] = 0;
        for (int i = 2; i <= 10000; i++) build(i);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &k);
            root[i] = add(root[i - 1], k);
        }
        int l1, r1, l2, r2, x;
        while (m--) {
            scanf("%d%d%d%d%d", &l1, &r1, &l2, &r2, &x);
            if (x > 10000) {
                printf("0\n0\n0\n");
                continue;
            }
            if (l1 > r1) swap(l1, r1);
            if (l2 > r2) swap(l2, r2);
            int a = query(root[r1], x) - query(root[l1 - 1], x);
            int b = query(root[r2], x) - query(root[l2 - 1], x);
            int c = 1LL * a * b % MOD;
            printf("%d\n%d\n%d\n", a, b, c);
        }
        return 0;
    }
    View Code

    优化后一棵树的大小为10000,每个节点都有效。但是由于路径问题,还是有一些重复的节点无法删除。

I .y大的字符串

sol:Trie+manacher,比赛的时候没开这题有点可惜,Trie是我最早学的数据结构,敲的很熟练但是没在比赛中碰到过。这次还是比赛结束之后看到题解补的。

ps:数据还是有问题,1M的字符串长度是1024 * 1024。但是字符串远不止这个长度。

  • Trie + manacher
    #include "cstdio"
    #include "cstdlib"
    #include "cstring"
    #include "algorithm"
    using namespace std;
    const int MAXN = 10250 * 1025;
    char s[15], t[MAXN * 2];
    bool head[MAXN];
    int p[MAXN * 2];
    int _max;
    struct Trie {
        Trie* next[26];
        bool tail;
    } *root;
    int max(int a, int b) {return a > b ? a : b;}
    int min(int a, int b) {return a < b ? a : b;}
    Trie* init() {
        Trie* p = (Trie*)malloc(sizeof(Trie));
        memset(p->next, NULL, sizeof(p->next));
        p->tail = false;
        return p;
    }
    void insert(char* s) {
        Trie* p = root;
        for (int i = 0; s[i]; i++) {
            int j = s[i] - 'a';
            if (!p->next[j]) p->next[j] = init();
            p = p->next[j];
        }
        p->tail = true;
    }
    void search(char* s) {
        Trie* p = root;
        for (int i = 0; s[i]; i++) {
            int j = s[i] - 'a';
            if (!p->next[j]) return;
            p = p->next[j];
            if (p->tail) {
                head[s + i - t + 1] = true;
                _max = max(_max, s + i - t + 1);
            }
        }
    }
    int manacher(char* s, int* p) {
        int n = strlen(s);
        for (int i = n; i >= 0; i--) {
            s[i + 1 << 1] = s[i];
            s[i << 1 | 1] = '#';
        }
        s[0] = '$';
        n = n + 1 << 1;
        int _max = 1, k = 0;
        for (int i = 2; s[i]; i++) {
            if (i >= k + p[k]) p[i] = 1;
            else p[i] = min(p[2 * k - i], k + p[k] - i);
            while (s[i + p[i]] == s[i - p[i]]) p[i]++;
            if (p[i] + i > p[k] + k) k = i;
            _max = max(_max, p[i]);
        }
        return _max - 1;
    }
    int main() {
        int n, m;
        scanf("%d%d", &n, &m);
        root = init();
        while (n--) {
            scanf("%s", s);
            insert(s);
        }
        while (m--) {
            scanf("%s", t);
            _max = 0;
            memset(head, false, sizeof(head));
            head[0] = true;
            for (int i = 0; t[i]; i++) if (head[i]) search(t + i);
            t[_max] = '\0';
            printf("%d %d\n", _max, manacher(t, p));
        }
        return 0;
    }
    View Code

    牛客的库函数里居然没有min和max。

J .外挂

sol:推出公式后用线段树维护,推公式需要比较强的数学基础,但是线段树还是可以敲的;

  • 线段树
    #include "bits/stdc++.h"
    using namespace std;
    typedef long long LL;
    const LL MAXN = 1e5 + 5;
    struct Node {
        LL l, r;
        // s1表示区间和,s2表示区间平方和 
        LL s1, s2;
        LL lazy;
    } segTree[MAXN * 4];
    void push_up(LL p, LL x) {
        segTree[p].lazy += x;
        segTree[p].s2 += 2 * segTree[p].s1 * x + x * x * (segTree[p].r - segTree[p].l + 1);
        segTree[p].s1 += x * (segTree[p].r - segTree[p].l + 1);
    }
    void push_down(LL p) {
        if (segTree[p].l == segTree[p].r) return;
        push_up(p << 1, segTree[p].lazy);
        push_up(p << 1 | 1, segTree[p].lazy);
        segTree[p].lazy = 0;
    }
    void build(LL p, LL l, LL r) {
        segTree[p].l = l;
        segTree[p].r = r;
        if (l == r) {
            scanf("%lld", &segTree[p].s1);
            segTree[p].s2 = segTree[p].s1 * segTree[p].s1;
            return;
        }
        LL mid = l + r >> 1;
        build(p << 1, l, mid);
        build(p << 1 | 1, mid + 1, r);
        segTree[p].s1 = segTree[p << 1].s1 + segTree[p << 1 | 1].s1;
        segTree[p].s2 = segTree[p << 1].s2 + segTree[p << 1 | 1].s2;
    }
    void add(LL p, LL l, LL r, LL x) {
        if (segTree[p].l >= l && segTree[p].r <= r) {push_up(p, x); return;}
        push_down(p);
        LL mid = segTree[p].l + segTree[p].r >> 1;
        if (l <= mid) add(p << 1, l, r, x);
        if (r > mid) add(p << 1 | 1, l, r, x);
        segTree[p].s1 = segTree[p << 1].s1 + segTree[p << 1 | 1].s1;
        segTree[p].s2 = segTree[p << 1].s2 + segTree[p << 1 | 1].s2;
    }
    LL getS1(LL p, LL l, LL r) {
        if (segTree[p].l == l && segTree[p].r == r) return segTree[p].s1;
        push_down(p);
        LL mid = segTree[p].l + segTree[p].r >> 1;
        if (r <= mid) return getS1(p << 1, l, r);
        if (l > mid) return getS1(p << 1 | 1, l, r);
        return getS1(p << 1, l, mid) + getS1(p << 1 | 1, mid + 1, r);
    }
    LL getS2(LL p, LL l, LL r) {
        if (segTree[p].l == l && segTree[p].r == r) return segTree[p].s2;
        push_down(p);
        LL mid = segTree[p].l + segTree[p].r >> 1;
        if (r <= mid) return getS2(p << 1, l, r);
        if (l > mid) return getS2(p << 1 | 1, l, r);
        return getS2(p << 1, l, mid) + getS2(p << 1 | 1, mid + 1, r);
    }
    int main() {
        LL n, m;
        scanf("%lld%lld", &n, &m);
        build(1, 1, n);
        LL op, l, r, x;
        while (m--) {
            scanf("%lld", &op);
            if (op == 1) {
                scanf("%lld%lld%lld", &l, &r, &x);
                add(1, l, r, x);
            } else {
                scanf("%lld%lld", &l, &r);
                LL s1 = getS1(1, l, r);
                LL s2 = getS2(1, l, r);
                printf("%lld\n", s1 * s1 - s2 >> 1);
            }
        }
        return 0;
    } 
    View Code

     

posted @ 2019-06-17 22:20  Jathon-cnblogs  阅读(280)  评论(0编辑  收藏  举报