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;
}

原文地址:https://www.cnblogs.com/Silymtics/p/14534454.html