从删库到跑路:我的算法救赎之路 Day1

本专栏主要为了巩固基础算法,会把比赛中觉得比较好的题目拿过来整理一下。

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值