作业题解

A

前缀和上课讲过了,说一下 $dp$。

思路很简单,$dp_i$ 为以 $i$ 结尾的最大子段和。

分类讨论前面的数取不取,则有 $dp_i=\max(a_i,a_i+dp_{i-1})$。

那么答案就是 $\max dp_i$。

#include <iostream>
using namespace std;
int n, m = -1e9, a[200050], dp[200050];
int main()
{
    cin >> n;
    for(int i = 1;i <= n;++i)
    {
        cin >> a[i];
        dp[i] = max(a[i], a[i] + dp[i - 1]);
        m = max(m, dp[i]);
    }
    cout << m;
    return 0;
} 

B

虽然 cdcq 说这个不是讲课内容,但老崔还是出了

考虑枚举左上右下角,加上求和复杂度 $O(n^6)$。

前缀和优化掉 $O(n^2)$,复杂度 $O(n^4)$,还是不太行。

考虑上一个题的思路,把二维变成一维。

首先,在矩阵上划定一个范围:

枚举这个范围需要 $n^2$ 的复杂度,子矩阵将在这个范围中确定。

将这个范围中每行的和算出来:

$s_i$ 表示第 $i$ 行每个数的和,灰框是一个临时的数组。

求每行的和需要 $nm$ 的复杂度,但可以用前缀和优化成 $O(m)$。

那么,这个范围就被抽象成了灰框内的数组。

算这个数组的最大子段和,最大的子段就对应着最大的矩阵。

这样,最大矩阵就找到了,复杂度 $O(n^2m)$。

其实这段是从以前的题解里复制出来的

#include <iostream>
using namespace std;
int s[150][150], n, t[150], ans = -1e9;
int main()
{
    cin >> n;
    for(int i = 1, t;i <= n;++i)
        for(int j = 1;j <= n;++j)
            cin >> t, s[i][j] = s[i][j - 1] + t;
    for(int i = 1;i <= n;++i)
        for(int j = i;j <= n;++j)
        {
            for(int k = 1;k <= n;++k)
                t[k] = s[k][j] - s[k][i - 1];
            for(int k = 1;k <= n;++k)
                t[k] = max(t[k], t[k - 1] + t[k]), ans = max(ans, t[k]);
        }
    cout << ans;
    return 0;
}

这题有个双倍经验,绿的

C

上课讲了离散化,实际上有一个更简单的方法。

实际上,把 $a_i,b_i$ 分别排序后对答案没有影响。

    ________      _____
   |   __   |排序|   __|__
   |  |  |  |--->|  |  |  |

把排序后每一段的长度加起来,每次减去这一段和上一段的重复部分。

#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
int a[20050], b[200050], n, ans;
signed main()
{
    cin >> n;
    for(int i = 0;i < n;++i)
        cin >> a[i] >> b[i];
    sort(a, a + n);
    sort(b, b + n);
    for(int i = 0;i < n;++i)
    {
        ans += b[i] - a[i];
        if(i && a[i] < b[i - 1])
            ans -= b[i - 1] - a[i];
    }
    cout << ans;
    return 0;
}

D

这个题跟之前考试那个 $(A-B)|200$ 挺像的。

转化成 $A-C=B$。

把 $a_i$ 扔进个桶,判断有多少个 $a_i-c$ 在桶里即可。

#include <iostream>
#include <unordered_map>
#define int long long
using namespace std;
int a[200001], n, c, s;unordered_map<int, int> m;
signed main()
{
    cin >> n >> c;
    for(int i = 0;i < n;++i)
        cin >> a[i], ++m[a[i]];
    for(int i = 0;i < n;++i)
        s += m[a[i] - c];
    cout << s;
    return 0;
}

E

树上前缀和板子。$s_i$ 表示从根到 $i$ 点边权的异或和。

但由于异或的特殊性(逆运算是本身),

原来的 $s_x\oplus s_y\oplus s_{lca(x,y)}\oplus s_{lca(x,y)}$ 直接变成了 $s_x\oplus s_y$。

就不需要 $LCA$ 了。dfs 求出 s 数组即可。

#include <iostream>
#include <list>
#include <utility>
using namespace std;
list<pair<int, int> > edge[100050];int n, m, s[100050];bool vis[100050];
void add(int u, int v, int w) {edge[u].push_back(make_pair(w, v));}
void dfs(int x, int dep)
{
    vis[x] = 1;s[x] = dep;
    for(auto &i : edge[x])
        if(!vis[i.second])
            dfs(i.second, dep ^ i.first);
}
int main()
{
    cin >> n;
    for(int i = 1, u, v, w;i < n;++i)
        cin >> u >> v >> w, add(u, v, w), add(v, u, w);
    dfs(1, 0);cin >> m;
    for(int i = 0, l, r;i < m;++i)
        cin >> l >> r, cout << (s[l] ^ s[r]) << endl;
    return 0;
}

F

这不就树状数组差分建树板子吗

#include <iostream>
using namespace std;
#define lbt(i) (i & -i)
int c[5000050], n, p, a[5000050], ans = 1e9;
void chg(int x, int k) {for(int i = x;i <= n;i += lbt(i)) c[i] += k;}
int ask(int x) {int ret = 0;for(int i = x;i;i -= lbt(i)) ret += c[i];return ret;}
int main()
{
    cin >> n >> p;
    for(int i = 1;i <= n;++i)
        cin >> a[i], c[i] = a[i] - a[i - lbt(i)];
    for(int i = 0, x, y, z;i < p;++i)
        cin >> x >> y >> z, chg(x, z), chg(y + 1, -z);
    for(int i = 1;i <= n;++i)
        ans = min(ans, ask(i));
    cout << ans;
    return 0;
}

但是数据到 $10^7$ 之后,$O(n\log n)$ 的树状数组就没辙了。

这题只有一次查询,所以考虑差分数组。

#include <iostream>
using namespace std;
int d[5000050], n, p, a[5000050], ans = 1e9;
int main()
{
    cin >> n >> p;
    for(int i = 1;i <= n;++i)
        cin >> a[i], d[i] = a[i] - a[i - 1];
    for(int i = 0, x, y, z;i < p;++i)
        cin >> x >> y >> z, d[x] += z, d[y + 1] -= z;
    for(int i = 1;i <= n;++i)
        a[i] = a[i - 1] + d[i], ans = min(ans, a[i]);
    cout << ans;
    return 0;
}

这样修改就是 $O(1)$ 了。

posted @ 2022-01-15 09:31  5k_sync_closer  阅读(23)  评论(0编辑  收藏  举报  来源