本专栏主要为了巩固基础算法,会把比赛中觉得比较好的题目拿过来整理一下。
一、D-收集金币_牛客小白月赛111
题⽬来源: 牛客小白月赛111
难度系数: ★★


【解题】:

🖥️code:
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1010, INF = 0x3f3f3f3f;
LL f[N][N], a[N][N], ti[N][N];
int n, m, t;
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
cin >> a[i][j];
}
for(int i = 0; i <= n; i++)
for(int j = 0; j <= m; j++)
{
f[i][j] = -INF;
ti[i][j] = INF;
}
cin >> t;
for(int i = 1; i <= t; i++)
{
int x, y, v; cin >> x >> y >> v;
ti[x][y] = v;
}
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
if(i + j - 1 - 1 >= ti[i][j]) a[i][j] = -INF;
}
}
f[0][1] = 0;
LL ret = -INF;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
f[i][j] = max(f[i][j - 1], f[i - 1][j]) + a[i][j];
ret = max(ret, f[i][j]);
}
}
cout << ret << endl;
return 0;
}
二、B-小柒的逆序对(一)_牛客练习赛135
题⽬来源: 牛客练习赛135
难度系数: ★★
【解题】:本题的数据为1e5,只能选择时间复杂度优于n^2的算法,所以我们不能交换一次就求一次逆序对的数量,再观察题目中的数据,一开始竟然给出了逆序对的数量,再次验证本题不是考怎么求逆序对,而是求交换两个数之后逆序对怎么变化,再次观察数据不难想出这个数量一定只与交换的两个位置有关,结合样例很容易蒙出:
当a[x] < a[y] 时,m++;
当a[x] > a[y] 时, m--;
注意x < y。没错本题就是这么做QAQ,下面是证明:
🖥️code:
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL n, m, q;
int a[N];
int main()
{
cin >> n >> m >> q;
for(int i = 1; i <= n; i++) cin >> a[i];
while(q--)
{
int x, y; cin >> x >> y;
if(x > y) swap(x, y);
if(a[x] < a[y]) m++;
else if(a[x] > a[y]) m--;
swap(a[x], a[y]);
if(m % 2) cout << "odd" << endl;
else cout << "even" << endl;
}
return 0;
}
三、E-小红的陡峭值(四)_牛客周赛 Round 84
题⽬来源:牛客周赛 Round 84
难度系数: ★★

说点废话:当学长给我说这题是暴力的时候我都蒙了,我还在想是不是应该会和红黑树有关QAQ。本题的算法思想:暴力,前缀和。
【解题】:前缀和数组f[i]:表示以i为根节点的树的陡峭值。虽然题目中给的是无根树,但是我们仍旧可以把它当做
以1为根节点的有根树处理,原因是两树的陡峭值之差与整棵树谁是根节点并无关系。
问题一:如何预处理前缀和数组? 对树来一遍dfs,加上f[u] += abs(u - x) + f[x]就好。
问题二:如何得到最终答案?是的再来一遍dfs,我们的暴力想法是每个边都切一遍,而每次向下递归时都会得到它的子结点,因此f[u] - f[v] - abs(u - v)(u为根,v为子)表示就是切掉边之后的陡峭值,再次减掉原子树的陡峭值f[v]就是最终结果,ret = min(ret, abs(f[x] - (f[1] - f[x] - abs(u - x))));
🖥️code:
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL n, st[N], f[N];
vector<LL> edges[N];
LL ret = 0x3f3f3f3ff3f3f3f;
void dfs(int u)
{
st[u] = true;
for (auto x : edges[u])
{
if (!st[x])
{
// f表示以u为根节点的子树的陡峭值
dfs(x);
f[u] += abs(u - x) + f[x];
}
}
}
void dfs1(int u)
{
st[u] = true;
for (auto x : edges[u])
{
if (!st[x])
{
LL t = abs(f[x] - (f[1] - f[x] - abs(u - x)));
ret = min(ret, t);
dfs1(x);
}
}
}
// 5 1 2 2 3 3 4 4 5
int main()
{
cin >> n;
for (int i = 1; i < n; i++)
{
int u, v; cin >> u >> v;
edges[u].push_back(v);
edges[v].push_back(u);
}
dfs(1);
// for(int i = 1; i <= n; i++) cout << f[i] << " ";
// cout << endl;
memset(st, 0, sizeof st);
dfs1(1);
cout << ret << endl;
return 0;
}
四、P1111 修复公路 - 洛谷
题⽬来源: 洛谷
难度系数: ★

说点废话:本题为今天考核的题目,说实话被二分忽悠傻了,这题完全可以用并查集做。
【解题一】:所有村庄完全被联通 -> 村庄最早合并成一个集合 -> 排序扔并查集里。
问题一:如何判断是否完全连通? 倒是可以没扔一次,扫一遍fa数组,但是这样不优雅,所有村庄都连通意味着所有的编号都至少出现一次,我们可以在都出现一次后查。
🖥️code:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e3 + 10, M = 1e5 + 10;
int fa[N], check[N];
int cnt;
int n, m;
struct node {
int x, y, t;
}a[M];
bool cmp(node& x, node& y)
{
return x.t < y.t;
}
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void un(int x, int y)
{
int fx = find(x), fy = find(y);
fa[fx] = fy;
}
int solve()
{
for (int i = 1; i <= m; i++)
{
int x = a[i].x, y = a[i].y, t = a[i].t;
if (check[x]++ == 0) cnt++;
if (check[y]++ == 0) cnt++;
un(x, y);
if (cnt == n)
{
int ret = 0;
for (int i = 1; i <= n; i++)
if (fa[i] == i) ret++;
if (ret == 1) return t;
}
}
return -1;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= m; i++)
{
cin >> a[i].x >> a[i].y >> a[i].t;
}
sort(a + 1, a + 1 + m, cmp);
cout << solve() << endl;
return 0;
}
【解题二】:二分答案也是可以解本题的,我们规定check函数在mid时间内是否能全部连通。
问题一:如何判断是否全部连通? 建树 + dfs遍历,全部遍历返回true,否则返回false。
问题二:如何处理到最后也没有连通的情况? 只需要最后check一下l即可。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1e3 + 10, M = 1e5 + 10;
int n, m;
struct node {
int x, y, t;
}a[M];
vector<int> edges[N];
bool st[N];
bool cmp(node& x, node& y)
{
return x.t < y.t;
}
void dfs(int u)
{
st[u] = true;
for (auto x : edges[u])
{
if (!st[x]) dfs(x);
}
}
bool check(int mid)
{
// 清空数据
for (int i = 1; i <= n; i++)
{
edges[i].clear();
st[i] = 0;
}
// 建树
for (int i = 1; i < m; i++)
{
int x = a[i].x, y = a[i].y, t = a[i].t;
if (mid >= t)
{
edges[x].push_back(y);
edges[y].push_back(x);
}
else break;
}
// dfs
dfs(1);
for (int i = 1; i <= n; i++) if (!st[i]) return false;
return true;
}
int main()
{
cin >> n >> m;
int l = 0, r = 1e6 + 10;
for (int i = 1; i <= m; i++)
{
int x, y, t; cin >> x >> y >> t;
a[i] = { x, y, t };
}
sort(a + 1, a + 1 + m, cmp);
while (l < r)
{
int mid = (r + l) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
if (check(l)) cout << l << endl;
else cout << -1 << endl;
return 0;
}