zoukankan      html  css  js  c++  java
  • 20210314 简单题专场

    写在前面

    真的是简单题了

    码量也不大,两道洛谷原题,龙终于良心了一回(泪目!)

    但还有套更难的/jk (虽然没考,但我一会不会


    期望得分:\(100pts + 100pts + 100pts = 300pts\)
    实际得分:\(100pts + 100pts + 50pts = 250pts\)

    丢的 \(50\) 分竟然是多了一个 \(+1\)

    wtcl /kk

    T1 序列合并

    Description

    有两个长度都为 \(N\) 的序列 \(A\)\(B\),在 \(A\)\(B\) 中各取一个数相加可以得到 \(N^2\) 个和,求这 \(N^2\) 个和中最小的 \(N\) 个。数据范围:\(N \le 1e5\)
    保证两个序列单调不降

    P1631 序列合并

    Solution

    \(N^2\) 显然不可取,可以直接根据序列的性质来做

    \(f_{i, j}\) 表示 \(A\) 序列的第 \(i\) 个元素和 \(B\) 序列的第 \(j\) 个元素的和(设个 \(f\) 只是为了方便理解,不用写进代码)
    根据所给的序列的性质会发现 \(f_{i, j} \le f_{i, j + 1}\) 一定成立
    注意如果 \(i\) 不同,比较起来较为麻烦

    考虑维护一个优先队列,并不是把 \(n^2\) 个数都放进去,
    先放进去所有 \(f_{i, 1}(1 \le i \le n)\),然后每次取出队首就是当前的答案之一,直接输出即可
    设取出的数为 \(f_{i, j}\),那么把它弹出后再把 \(f_{i, j + 1}\) 放进去, 循环 \(n\) 次;

    正确性?应该比较显然。
    因为 \(f\) 每一行第一个数最小,而行与行之间又不方便比较,所以初始化要塞进 \(f\) 中每一行的第一个元素

    Code

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<string>
    #include<queue>
    #include<bitset>
    
    using namespace std;
    const int MAXN = 1e5+5;
    const int INF = 1e9+7;
    const int mod = 1e9+7;
    
    struct node{
        int fir, sec, val;
        bool operator < (const node &b) const { return val > b.val; }
    };
    
    int n;
    int a[MAXN], b[MAXN];
    priority_queue<node> q;
    
    int read() {
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
        return f ? -s : s;
    }
    
    int main()
    {
        freopen("sequence.in","r",stdin);
        freopen("sequence.out","w",stdout);
        n = read();
        for(int i = 1; i <= n; ++i) a[i] = read();
        for(int i = 1; i <= n; ++i) b[i] = read();
        for(int i = 1; i <= n; ++i) q.push((node){i, 1, a[i] + b[1]});
        for(int i = 1; i <= n; ++i) {
            node t = q.top(); q.pop();
            q.push((node){t.fir, t.sec + 1, a[t.fir] + b[t.sec + 1]});
            printf("%d ", t.val);
        }
        return 0;
    }
    
    

    T2 扩散

    Description

    P1661 扩散

    Solution

    最先与每个点联通的肯定是离他最近的点
    所以答案应该是一个点与离他最近的点的距离最远的时候
    考虑把 \(n\) 个点之间的距离关系都记录下来建图,跑一棵最小生成树,生成树上最后练的那一条边就是要求的答案

    两个点 \(a, b\) 相连同所需时间是

    \[\frac{\mid a_x - b_x \mid + \mid a_y + b_y \mid + 1}{2} \]

    为什么加一?手模几个样例就知道了

    Code

    /*
    对于每一个点来说,肯定最先和里他最近的点相连
    最小生成树?
    找到第n - 1条边就是答案? 
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<string>
    
    using namespace std;
    const int MAXN = 1e3+5;
    const int INF = 1e9+7;
    const int mod = 1e9+7;
    
    struct edge{
        int from, to, w;
    }e[MAXN * MAXN];
    int num_edge = 0; 
    bool cmp(edge x, edge y) { return x.w < y.w; }
    
    int n, cnt, ans;
    int fa[MAXN];
    int sx[MAXN], sy[MAXN];
    
    int read() {
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
        return f ? -s : s;
    }
    
    void add_edge(int from, int to, int w) { e[++num_edge] = (edge){from, to, w}; }
    int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
    int Calc(int sx, int sy, int ex, int ey) { return (abs(sx - ex) + abs(sy - ey) + 1) / 2; } // 加 1 是为了修正计算结果 (应该没算错 
    
    int main()
    {
        freopen("spread.in","r",stdin);
        freopen("spread.out","w",stdout);
        n = read();
        for(int i = 1; i <= n; ++i) sx[i] = read(), sy[i] = read();
        for(int i = 1; i < n; ++i) {
            for(int j = i + 1; j <= n; ++j) {
                int dis = Calc(sx[i], sy[i], sx[j], sy[j]);
                add_edge(i, j, dis);
            }
        }
        sort(e + 1, e + num_edge + 1, cmp);
        for(int i = 1; i <= n; ++i) fa[i] = i;
        for(int i = 1; i <= num_edge; ++i) {
            int uf = find(e[i].from), vf = find(e[i].to);
            if(uf != vf) {
                fa[uf] = vf;
                cnt ++;
                if(cnt == n - 1) {
                    ans = e[i].w;
                    break;
                }
            }
        }
        printf("%d", ans);
        return 0;
    }
    
    

    T3 攻击策略

    Description

    植物大战僵尸这款游戏中,还有一个特别的玩儿法:玩家操纵僵尸进攻植物。
    首先,僵尸有 \(m\) 种(每种僵尸都是无限多的),玩家可以选择合适的僵尸来进攻。使用第 \(i\) 种僵尸需要花费 \(W_i\)资源,可以得到 \(P_i\) 的攻击效果。在这里,我们认为多个僵尸总的攻击效果就是他们每个攻击效果的代数和。
    地图共有 \(n\) 行,对于第 \(i\) 行,最左端有若干植物,这些植物需要至少 \(Q_i\) 的攻击才能被全部消灭。若一行上的植物全部被消灭,我们称这一行被攻破。
    由于资源紧张,你只有总量为 \(K\) 的资源,不一定能够攻破所有行。但统治者希望攻破相邻的 \(T\) 行,并希望 \(T\) 尽量的大。你能帮他算出 \(T\) 的值吗?
    数据范围:\(n \le 2e5,m \le 100,K \le 1e3,Pi、Qi \le 1e8\)

    懒得简化题面了

    Solution

    要求 \(T\) ,而且还不好确定?
    考虑二分一下 \(T\)
    每次判断的时候 \(O(n)\) 扫一遍整个区间,看看有没有长度为 \(T\) 的区间所需的花费小于 \(K\) 即可
    求区间和时可以 \(O(1)\) 做,维护一个前缀和即可

    现在让我们考虑求出每一行所需的最小花费
    发现求攻击效果到达 \(Q_i\) 时的花费复杂度太高
    不如先求出 \(x\) 的花费最多能达到多大的攻击效果
    \(x\) 看做背包容量,\(m\) 种僵尸看做物品,\(W_i\) 当做花费, \(P_i\) 当做价值,跑一个裸的完全背包即可

    发现这个玩意一定是单调不降的,直接用 lower_bound 函数求出每一行的最小花费即可(即要获得 \(Q_i\) 的价值最小要多少花费,下标刚好就是要求的花费)

    但是!
    这个 lower_bound 的区间和平常不太一样,因为花费可以为 \(0\)(或者说价值为 \(0\) 是不需要花费),所以应该改成 lower_bound(f, f + K + 1, Qi) - f 的形式
    就因为这个小错误,五十分啊!!!

    现在要处理的都处理出来了,二分答案即可

    Code

    /*
    二分 T 值
    O(n) 判断是否可行
    总复杂度 O(nlogn) 
     
    预处理出每一行被攻破所需的最小花费 
    
    发现K,m很小
    跑一遍多重背包获得花费 x 最多获得的攻击力 
    (理论上来说是单调不降的?所以对于每行所需最小花费可以log查询了? 
    
    总复杂度O(2nlogn + mK) 
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<string>
    
    using namespace std;
    const int MAXN = 2e5+5;
    const int INF = 1e9+7;
    const int mod = 1e9+7;
    
    int n, m, K, ans;
    int cost[MAXN], val[MAXN];
    int f[MAXN];
    int a[MAXN], ned[MAXN], sum[MAXN];
    
    int read() {
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
        return f ? -s : s;
    }
    
    bool check(int mid) {
        for(int i = 0; i <= n - mid; ++i) 
            if(sum[i + mid] - sum[i] <= K) return true;
        return false;
    }
    
    int main()
    {
        freopen("attack.in","r",stdin);
        freopen("attack.out","w",stdout);
        m = read(), n = read(), K = read();
        for(int i = 1; i <= m; ++i) cost[i] = read();
        for(int i = 1; i <= m; ++i) val[i] = read();
        for(int i = 1; i <= n; ++i) a[i] = read();
        for(int i = 1; i <= K; ++i) { // 枚举背包容量 
            for(int j = 1; j <= m; ++j){ // 枚举所用的僵尸 
                if(i - cost[j] < 0) continue; // 多重背包跑法 
                f[i] = max(f[i], f[i - cost[j]] + val[j]);
            }
        }
        for(int i = 1; i <= n; ++i) {
            ned[i] = lower_bound(f, f + K + 1, a[i]) - f; //找到攻破每一行所需的最小花费 
            sum[i] = sum[i - 1] + ned[i]; // 维护一个前缀和,方便求区间和 
        }
        int L = 0, R = n;
        while(L <= R) {
            int mid = (L + R) >> 1;
            if(check(mid)) ans = mid, L = mid + 1;
            else R = mid - 1;  
        }
        printf("%d", ans);
        return 0;
    }
    
    
  • 相关阅读:
    SQL Server
    SQL Server
    SQL Server
    SQL Server
    SQL Server
    SQL Server
    SQL Server
    ssh保持连接不断开
    如何查看linux是否打开虚拟化
    dd命令详解
  • 原文地址:https://www.cnblogs.com/Silymtics/p/14534454.html
Copyright © 2011-2022 走看看