WHU Winter Camp Contest 5 (for Div 2) 题解

Time: 2018.1.26 12:30 ~ 17:30

1 Happy Three Friends

题目来源 HDU - 4931

1.1 题目描述

给你6个数,一个人从中选2个,一个人从剩下的4个中任选3个,问你第一个人的总和能不能大于第二个人的总和。

1.2 题解

水题,直接排序就可以。

2 Maximum Increase

题目来源 CodeForces - 702A

2.1 题目描述

找出给定序列中的最长递增子串。

2.2 题解

水题,直接遍历一遍维护最大值就可以。注意退出循环后还需要更新一遍最大值。

3 Inna and Choose Options

题目来源 CodeForces - 400A

3.1 题目描述

给你一个仅包含X和O的长度为12的字符串,然后将其按顺序拆成一个axb(比如1x12,2x6)的字符串矩阵,问你这个矩阵存不存在一列全是X。

3.2 题解

水题,由于可能的情况很少,直接枚举就可以了。

4 DZY Loves Chemistry

题目来源 CodeForces - 445B

4.1 题目描述

有n种化学试剂,编号1到n,然后告诉你m对发生化学反应的化学试剂编号。当往杯子里倒入一种试剂时,如果杯子中存在一种会和其反应的试剂,则危险系数会翻倍。空杯子的危险系数定义为1,问你最大的危险系数是多少。

4.2 题解

这是一道并查集的基本题目。虽然化学试剂的反应没有传递性(因为并查集的连接关系都具有传递性,比如A和B是家人,B和C是家人,那么A和C也是家人),但注意到加入顺序并没有限制性,所以说同一个集合内的所有试剂之间其实组成了一个连通图,那么一定有某种添加顺序,让试剂添加进去时一定有和其能反应的试剂在里面(空杯子除外)。
所以这道题本质上就是并查集的应用,同时维护集合的大小,最后得到答案。

4.3 代码
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <unordered_set>
#include <unordered_map>
using namespace std;

int n, m;
int root[55], size[55];

int Find(int x) {
    if (root[x] == x) return x;
    return root[x] = Find(root[x]); // 路径压缩
}

void Union(int x, int y) {
    int u = Find(x);
    int v = Find(y);
    if (u != v) {
        root[v] = u;
        size[u] += size[v];
    }
}


int main() {
    //freopen("H://input.txt", "r", stdin);
    scanf("%d %d", &n, &m);

    for (int i = 1; i <= n; i++) {
        root[i] = i;
        size[i] = 1;
    }

    for (int i = 0; i < m; i++) {
        int x, y;
        scanf("%d %d", &x, &y);
        Union(x, y);
    }

    long long ans = 1;
    for (int i = 1; i <= n; i++) {
        if (root[i] == i) {
            ans *= pow(2, size[i]-1);
        }
    }

    printf("%lld\n", ans);
    return 0; 
}
5 Queue

题目来源 CodeForces - 490B

5.1 题目描述

n个人排队,每个人都有不同的编号,每个人告诉你他前面的一个人是谁,后面的一个人是谁,但是不告诉你他自己是谁。如果是第一个人或者是最后一个人,就告诉你编号0。现在让你恢复这个队伍的顺序。

5.2 题解

很有意思的一道题目。我们定义front[i]表示编号为i的人的前面的前面的人。如果front[i]为0,就说明这个人是第二个,如果front[i]未定义,就说明这个人是第一个人。那么我们O(n)时间就可以得到这个队的第一个人和第二个人。同时我们定义behind[i]表示这个人的后面的后面的人,那我们从第一个人开始,逐渐把behind的人加入到队列里,直到遇到behind[i]等于-1说明此时就达到了队列的尾部了。

6 Destroying Array

题目来源 CodeForces - 722C

6.1 题目描述

给你n个数,然后告诉你n个下标顺序,按照这个顺序不断摧毁该下标的数,每一次摧毁都会把原有的数划分为多个字段,问你每一次摧毁之后所有字段的最大和是多少。n不超过100000,每一个数不超过10的9次方。

6.2 题解

如果不告诉我这题可以用并查集来做我还真想不出来。由于题目告诉我们每一个数都是非负数,所以把一个数组割开求最大子串和与把两个子数组合并求最大子串和相比,显然后者要简单的多。因此我们逆序考虑这道题。每恢复一个数,如果这个数相邻左边有集合,就把这两个集合合并(恢复的那一个数自己构成一个集合),如果这个数相邻右边有集合,同样也把这两个集合合并。那么子串的最大值就是要么原来的最大值,要么就是新合并之后产生的最大值,两者取一个max操作就好了,最后倒序输出。

6.3 代码
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <cstring>
using namespace std;
const int maxn = 1000000 + 1;
int n;
int active[100005];
int root[100005];
long long sum[100005];

int Find(int x) {
    if (root[x] == x) return x;
    return root[x] = Find(root[x]);
}

void Union(int x, int y) {
    int u = Find(x);
    int v = Find(y);
    if (u != v) {
        root[v] = u;
        sum[u] += sum[v];
    }
}
vector<long long> ans, orders;
int main() {
    //freopen("H://input.txt", "r", stdin);
    memset(active, false, sizeof(active));
    int num = 0;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &num);
        sum[i] = num;
        root[i] = i;
    }
    for (int i = 0; i < n; i++) {
        scanf("%d", &num);
        orders.push_back(num);
    }

    ans.push_back(0);
    long long pre = 0;
    for (int i = orders.size()-1; i >= 1; i--) {
        int idx = orders[i]-1;
        active[idx] = 1;
        if (idx > 0 && active[idx-1]) {
            Union(idx-1, idx);
        }
        if (idx < n-1 && active[idx+1]) {
            Union(idx, idx+1);
        }
        long long tmp = max(pre, sum[root[idx]]);
        ans.push_back(tmp);
        pre = tmp;

    }

    for (int i = ans.size()-1; i >= 0; i--) {
        printf("%lld\n", ans[i]);
    }
    return 0; 
}
6.4 题解2

如果正着看(相当于在线处理),每得到一个要摧毁的下标,就把某一区间一分为2,这样画出来像一棵树,因此本题可以用线段树来做(虽然并不是最优的做法)。
贴一段大神的线段树代码,当着我的面vim手敲一气呵成直接AC,秀了我一脸。
v1,v2,v3,v4分别表示区间最大子段和、区间最大前缀和、区间最大后缀和、区间和。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 100000 + 5;
const long long oo = 0x3f3f3f3f3f3f3f3fll;
long long v1[maxn * 4], v2[maxn * 4], v3[maxn * 4], v4[maxn * 4];
long long a[maxn];

#define ls(x) (( x ) << 1)
#define rs(x) (ls(x) | 1)

long long max3(long long a, long long b, long long c)
{
    long long mx = -oo;
    mx = max(mx, a);
    mx = max(mx, b);
    mx = max(mx, c);
    return mx;
}

void push_up(int x)
{
    v1[x] = max3(v1[ls(x)], v1[rs(x)], v3[ls(x)] + v2[rs(x)]);
    v2[x] = max3(-oo, v2[ls(x)], v4[ls(x)] + v2[rs(x)]);
    v3[x] = max3(-oo, v3[rs(x)], v3[ls(x)] + v4[rs(x)]);
    v4[x] = max(-oo, v4[ls(x)] + v4[rs(x)]);
}

void upd(int x, int l, int r, int p, long long v)
{
    if (l == r)
    {
        v1[x] = v2[x] = v3[x] = v4[x] = v;
        return;
    }
    int mid = (l + r) / 2;
    if (p <= mid)
        upd(ls(x), l, mid, p, v);
    else
        upd(rs(x), mid + 1, r, p, v);
    push_up(x);
}

void build(int x, int l, int r)
{
    if (l == r)
    {
        v1[x] = v2[x] = v3[x] = v4[x] = a[l];
        return;
    }
    int mid = (l + r) / 2;
    build(ls(x), l, mid);
    build(rs(x), mid + 1, r);
    push_up(x);
}

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    build(1, 1, n);
    for (int i = 1; i <= n; i++)
    {
        int t;
        scanf("%d", &t);
        upd(1, 1, n, t, -oo);
        printf("%lld\n", max(0ll, v1[1]));
    }
    return 0;
}
7 George and Job

题目来源 Code Forces - 467C

7.1 题目描述

n个数p1,p2,..,pn,从中选取k个长度为m的子串,让其元素和最大,问你这个最大的元素和是多少。

7.2 思路

显然是Dynamic Programming的题目。定义如下状态,从前i个数中,选取j个长度为m的子串所能获得的最大和。我原本的递推方程是这样想的

dp[i, j] = max{dp[s,j-1]+max[s+1, i]}, (j-1)*m <= s <= i-m;

上述方程的意思就是说我考虑第j个子串是如何选择的,那么前面j-1个子串的可选范围就是[(j-1)*m, i-m],然后在最后[s+1, j]中选择和最大的子串。但是这样的话显然是O(n3)的复杂度,在n最大不超过5000的情况下这个复杂度超过了一般测评机所能承受的运算量(1s - 1e8次运算),所以需要进行优化。注意到子串的长度是固定的,所以对于每一个下标来说,都能一一对应一个以其结尾的长度为m的子串,那么第j个子串我就考虑加不加这个串,递推方程优化如下

dp[i, j] = max{d[i-1, j], dp[i-m][j-1]+sum[i]};
//sum[i]就表示以第i个数结尾的长度为m的子串和
7.3 代码
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <cstring>
using namespace std;
const int maxn = 1000000 + 1;


int n, m, k;
int arr[5005];
long long dp[5005][5005]; //前i个数选j个长度为m的子区间的最大和,Dynamic Programming.
long long sum[5005];

int main() {
    //freopen("H://input.txt", "r", stdin);

    scanf("%d %d %d", &n, &m, &k);

    for (int i = 1; i <= n; i++) {
        scanf("%d", arr + i);
    }

    for (int i = 1; i <= m; i++) {
        sum[m] += arr[i];
    }
    for (int i = m + 1; i <= n; i++) {
        sum[i] = sum[i - 1] + arr[i] - arr[i-m];
    }

    for (int i = m; i <= n; i++) {
        for (int j = 1; j <= k; j++) {
            dp[i][j] = max(dp[i-m][j-1] + sum[i], dp[i-1][j]);
        }
    }


    printf("%lld\n", dp[n][k]);
    return 0;
}
7.4 优化

时间优化:注意到当我判断选不选第j个子串时,由于前面有了j-1个子串,那么i至少为(j-1)*m,又由于一共有k个子串,那么选了第j个子串之后还剩下k-j个子串,所以i最大为n-(k-j)*m。这样一来时间就被优化了。
空间优化:注意到对于选择j个子串来说,我只需知道j和j-1的状态,因此可以用一个2维的滚动数组来代替原来5000x5000的long long 数组。
大牛优化过的代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 5000 + 5;

long long dp[2][maxn];
long long arr[maxn], sum[maxn];

int main()
{
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", &arr[i]);
        sum[i] = sum[i - 1] + arr[i];
        if (sum[i] >= m)
            sum[i] -= arr[i - m];
    }
    //i & 1得其奇偶性,(i & 1)^1得其 相反的奇偶性
    for (int i = 1; i <= k; i++)
    {
        dp[i & 1][i * m - 1] = 0;
        for (int j = i * m; j <= n - (k - i) * m; j++)
            dp[i & 1][j] = max(
                    dp[i & 1][j - 1],
                    dp[(i & 1) ^ 1][j - m] + sum[j]);
    }
    printf("%lld\n", dp[k & 1][n]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值