作业题解
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)$ 了。