算法入门刷题笔记 Day10 - E - Dijkstra? -- L - 做题做累了?点我玩个游戏放松一下8

写在前面

好久没更新公众号和博客了,因为最近在研究新的方向,所以很少发文。
笔者接触编程只有一年,这一年间主要研究启发式算法在运筹学中的应用。但是由于编程基础薄弱,在进一步研究复杂运筹学问题时发现基础算法不过关导致写出的代码运行速度很慢,因此很苦恼。所以决定这个暑假补习一下基础算法,主要是刷一些简单的ACM入门题。偶尔会发一些刷题笔记(偶尔!)。和作者有类似目标的同学可以一起交流共勉!

目前在看的教程:
北京理工大学ACM冬季培训课程

算法竞赛入门经典/刘汝佳编著.-2版可以在这里下载->github

课程刷题点
Virtual Judge

刷题代码都会放在github上,欢迎一起学习进步!

这两天主要在玩耍,玩耍之余做两道题目。

E - Dijkstra?

在这里插入图片描述
邻接表Dijkstra。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ll, int> pli;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
#define LOCAL

void swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}

const int maxn = 100000 + 5;
int vis[maxn];
ll dist[maxn];
int prenode[maxn];
vector<pii> g[maxn];
int n, m;

struct cmp {
	template<typename T, typename U>
	bool operator()(T const& left, U const& right) {
		if (left.second < right.second) return true;
		return false;
	}
};

void Dijkstra()
{
	priority_queue<pli, vector<pli>, cmp > q;
	memset(vis, 0, sizeof(vis));
	while (!q.empty())
		q.pop();
	for (int i = 0; i < n; i++)
		dist[i] = INT_MAX;
	memset(prenode, 0, sizeof(prenode));

	dist[0] = 0;
	q.push({ 0, 0 });
	while (!q.empty())
	{
		int now = q.top().second;
		q.pop();
		if (vis[now])
			continue;
		vis[now] = 1;
		for (auto to: g[now])
		{
			int nex = to.first;
			int weight = to.second;
			if (dist[now] + weight < dist[nex])
			{
				prenode[nex] = now;
				dist[nex] = dist[now] + weight;
				q.push({ dist[nex], nex });
			}
		}
	}

	if (dist[n - 1] == INT_MAX)
		cout << "-1" << endl;
	else
	{
		stringstream output;
		int i = n - 1;
		output << n << " ";
		while (prenode[i])
		{
			output << (prenode[i] + 1) << " ";
			i = prenode[i];
		}
		string str, s;
		while(output >> s) str += s + " ";
		str += "1";
		reverse(str.begin(), str.end());
		cout << str << endl;
	}
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
#endif
	cin >> n >> m;
	for (int i = 0; i < n; i++)
		g[i].clear();
	for (int i = 0; i < m; i++)
	{
		int u, v, w;
		cin >> u >> v >> w;
		u--;
		v--;
		g[u].push_back({ v, w });
		g[v].push_back({ u, w });
	}
	Dijkstra();
	return 0;
}

F - 拓扑排序·二

在这里插入图片描述
具体描述太长了,就不截图了,主要意思是在拓扑排序中加入类似斐波那契数列的加法,也就是入点的值加上出点的值。

deg[to]--;
virus[to] += virus[now];//这一句
if (!deg[to])
{
	vis[to] = 0;
	q.push(to);
}
#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, int> pdi;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
// #define LOCAL

void swap(int &x, int &y)
{
	int temp = x;
	x = y;
	y = temp;
}

const int maxn = 100000 + 5;
int deg[maxn];
int vis[maxn];
int virus[maxn];
vector<int> g[maxn];
queue<int> q;
int n, m, k;

bool topological()
{
	for (int i = 1; i <= n; i++)
	{
		if (!deg[i])
		{
			vis[i] = 1;
			q.push(i);
		}
	}
	int num = 0;
	while (!q.empty())
	{
		int now = q.front();
		q.pop();
		num++;
		for (auto to : g[now])
		{
			if (vis[to])
				continue;
			else
			{
				deg[to]--;
				virus[to] += virus[now];
				if (!deg[to])
				{
					vis[to] = 0;
					q.push(to);
				}
			}
		}
	}
	int sum = 0;
	for(int i = 1; i <= n; i++)
		sum += virus[i];
	cout << sum << endl;
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
#endif

	memset(vis, 0, sizeof(vis));
	memset(deg, 0, sizeof(deg));
	memset(virus, 0, sizeof(virus));
	for (int i = 0; i < n; i++)
		g[i].clear();
	while (!q.empty())
		q.pop();
	cin >> n >> m >> k;
	for (int i = 0; i < k; i++)
	{
		int id;
		cin >> id;
		virus[id]++;
	}
	for (int i = 0; i < m; i++)
	{
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		deg[v]++;
	}
	
	topological();
	return 0;
}

G - Arena Olympics

在这里插入图片描述
有点像以前玩过的一个小游戏。有一堆守卫,守卫的视野有一定范围(用角度衡量),如果你在别的守卫的视野中干掉另一个守卫,则会被发现。判断是否能干掉所有守卫。

抽象为图,看得到则连接一条有向边,然后进行拓扑排序,排序完成则可以干掉。

计算角度时发现cmath有一个 a t a n 2 ( y , x ) atan2(y, x) atan2(y,x)函数,范围为 ( − p i , p i ] (-pi, pi] (pi,pi],可以判断原点到另一点的arctan值。普通的 a t a n ( y / x ) atan(y/x) atan(y/x)函数,范围为 ( − p i / 2 , p i / 2 ] (-pi/2, pi/2] (pi/2,pi/2],这里选择atan2比较方便。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, int> pdi;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
#define pi 3.1415926
// #define LOCAL

void swap(int &x, int &y)
{
	int temp = x;
	x = y;
	y = temp;
}

const int maxn = 3000 + 5;
int deg[maxn];
int vis[maxn];
vector<int> order;
vector<int> g[maxn];
queue<int> q;
int n;
pii pos[maxn];
pii view[maxn];

bool topological()
{
	for (int i = 1; i <= n; i++)
	{
		if (!deg[i])
		{
			vis[i] = 1;
			q.push(i);
		}
	}
	order.clear();
	int num = 0;
	while (!q.empty())
	{
		int now = q.front();
		q.pop();
		order.push_back(now);
		num++;
		for (auto to : g[now])
		{
			if (vis[to])
				continue;
			else
			{
				deg[to]--;
				if (!deg[to])
				{
					vis[to] = 0;
					q.push(to);
				}
			}
		}
	}
	if (num == n)
		return true;
	else
		return false;
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
#endif
	memset(vis, 0, sizeof(vis));
	memset(deg, 0, sizeof(deg));
	for (int i = 0; i < n; i++)
		g[i].clear();
	while (!q.empty())
		q.pop();
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		int u, v, a, b;
		cin >> u >> v >> a >> b;
		pos[i] = {u, v};
		view[i] = {a, b};
	}
	for (int u = 1; u <= n; u++)
	{
		for (int v = 1; v <= n; v++)
		{
			if(u == v) continue;
			double tan = atan2((double)(pos[v].second - pos[u].second), (double)(pos[v].first - pos[u].first));
			if (tan > 0)
				tan = tan / pi * 180;
			else
				tan = (tan + 2 * pi) / pi * 180;
			if (tan > (double)(view[u].first - view[u].second) && tan < (double)(view[u].first + view[u].second))
			{
				g[u].push_back(v);
				deg[v]++;
			}
		}
	}
	if(topological())
		for (auto node : order)
			cout << node << " ";
	else 
		cout << "-1" << endl;
	return 0;
}

H - Shortest Cycle

在这里插入图片描述
求最短环。给一组数 a [ n ] a[n] a[n],若 a [ i ] A N D ( 位 与 运 算 ) a [ j ] a[i] AND(位与运算) a[j] a[i]ANDa[j]不为0,则存在一条权值为1的边。

全图中最短环肯定要用多源最短路Floyd,但有1e5个点, n 3 n^3 n3肯定超时。但观察可知,最多64*3(64位)中必定有三个数有一位数都为1,则三个数直接都有边,形成一个环。所以n>200的情况可以直接回答3。其余情况则使用Floyd。

Floyd检测环可以一边进行一边检测,在代码中给出。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, int> pdi;
#define INT_MAX 0x3f3f3f3f
#define INT_MIN 0x80000000
#define pi 3.1415926
// #define LOCAL

void swap(int &x, int &y)
{
	int temp = x;
	x = y;
	y = temp;
}

const int maxn = 1e3 + 5;
int n, a[maxn * 100];
int b[maxn * 100];
int mp[maxn][maxn], g[maxn][maxn], res;

void floyd()
{
	res = INT_MAX;
	for (int k = 1; k <= n; k++)
	{
		for (int i = 1; i < k; i++)
			for (int j = i + 1; j < k; j++)
				res = min(res, g[i][j] + mp[i][k] + mp[k][j]);
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				g[i][j] = g[j][i] = min(g[i][j], g[i][k] + g[k][j]);
	}
}
int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
#endif
	cin >> n;
	int m = 0;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		if (a[i] != 0)
			b[++m] = a[i];
	}
	n = m;

	if (n >= 200)
	{
		printf("3");
		return 0;
	}

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			if (i != j)
				mp[i][j] = g[i][j] = INT_MAX;

	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			if (i == j)
				continue;
			if ((b[i] & b[j]) != 0)
				g[i][j] = 1;
			mp[i][j] = g[i][j];
		}
	}

	floyd();
	if (res == INT_MAX)
		cout << -1 << endl;
	else
		cout << res << endl;
	return 0;
}

I - Missile Silos

在这里插入图片描述
求到达点s最短距离为L的点或边上的点。

单源最短路,但点可以出现在边上,需要求最短路后对边进行遍历。对边上是否有点到s最短距离为L需要进行判断,分别对边的两段u,v到s的最短距离判断后分类讨论,分类如代码中所示。

遍历变时不能遍历邻接表的边,因为这样会遍历两次。可以考虑累加后除2,或者再开一个数组记录边。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ll, int> pli;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
// #define LOCAL

void swap(int &x, int &y)
{
	int temp = x;
	x = y;
	y = temp;
}

const int maxn = 1e5 + 5;
int vis[maxn];
ll dist[maxn];
int prenode[maxn];
vector<pii> g[maxn];
int n, m, cap, L;

struct Arc
{
	int u, v, w;
} arc[maxn];

struct cmp
{
	template <typename T, typename U>
	bool operator()(T const &left, U const &right)
	{
		if (left.second < right.second)
			return true;
		return false;
	}
};

void Dijkstra()
{
	priority_queue<pli, vector<pli>, cmp> q;
	memset(vis, 0, sizeof(vis));
	while (!q.empty())
		q.pop();
	for (int i = 0; i < n; i++)
		dist[i] = INT_MAX;
	memset(prenode, 0, sizeof(prenode));

	dist[cap] = 0;
	q.push({0, cap});
	while (!q.empty())
	{
		int now = q.top().second;
		q.pop();
		if (vis[now])
			continue;
		vis[now] = 1;
		for (auto to : g[now])
		{
			int nex = to.first;
			int weight = to.second;
			if (dist[now] + weight < dist[nex])
			{
				prenode[nex] = now;
				dist[nex] = dist[now] + weight;
				q.push({dist[nex], nex});
			}
		}
	}
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
#endif
	cin >> n >> m >> cap;
	cap--;
	for (int i = 0; i < n; i++)
		g[i].clear();
	for (int i = 0; i < m; i++)
	{
		int u, v, w;
		cin >> u >> v >> w;
		u--, v--;
		g[u].push_back({v, w});
		g[v].push_back({u, w});
		arc[i].u = u, arc[i].v = v, arc[i].w = w;
	}
	cin >> L;
	Dijkstra();

	// for (int u = 0; u < n; u++)
	// cout << dist[u] << endl;

	int ans = 0;
	for (int i = 0; i < n; i++)
		if (dist[i] == L)
			ans++;
	for (int i = 0; i < m; i++)
	{
		int u = arc[i].u;
		int v = arc[i].v;
		int w = arc[i].w;
		if (dist[u] >= L && dist[v] >= L)
			continue;
		if (dist[u] >= L && dist[v] < L)
		{
			if (dist[v] + w > L)
				ans++;
		}
		else if (dist[v] >= L && dist[u] < L)
		{
			if (dist[u] + w > L)
				ans++;
		}
		else
		{
			int disu = L - dist[u], disv = L - dist[v];
			if (disu + disv == w)
				ans++;
			else
			{
				if (disu < w && dist[v] + (w - disu) > L)
					ans++;
				if (disv < w && dist[u] + (w - disv) > L)
					ans++;
			}
		}
	}

	printf("%d\n", ans);
	return 0;
}

J - Game Map

在这里插入图片描述

K - 【黄色】这题真的是模板题

在这里插入图片描述
Bellman-ford和SPFA都可以找负环,bellman我记得是第一冲循环n次后如果还有松弛则存在负环,SPFA是某个点入列n次(相当于松弛n次)则存在负环。直接抄了前面写的SPFA。

#include <iostream>
#include <sstream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <string>
#include <set>
#include <queue>
#include <map>
#include <stack>
#include <unordered_map>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, int> pdi;
#define INT_MAX 0x7fffffff
#define INT_MIN 0x80000000
// #define LOCAL

void swap(int &x, int &y)
{
	int temp = x;
	x = y;
	y = temp;
}

const int maxn = 1e3 + 5;
int vis[maxn];
ll dist[maxn];
vector<pii> g[maxn];
queue<int> q;
int n, m;
int cnt[maxn];

bool SPFA(int start)
{
	for (int i = 0; i < n; i++)
		dist[i] = INT_MAX;
	queue<int> q;
	dist[start] = 0;
	vis[start] = true;
	q.push(start);
	cnt[start]++;
	while (!q.empty())
	{
		int v = q.front();
		q.pop();
		vis[v] = false;

		for (int i = 0; i < g[v].size(); i++)
		{
			int nex = g[v][i].first;
			if (dist[nex] > dist[v] + g[v][i].second)
			{
				dist[nex] = dist[v] + g[v][i].second;
				if (vis[nex] == false)
				{
					q.push(nex);
					cnt[nex]++;
					vis[nex] = true;
					if (cnt[nex] > n)
						return false;
				}
			}
		}
	}
	return true;
}

int main()
{
#ifdef LOCAL
	freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
#endif
	memset(vis, 0, sizeof(vis));
	memset(cnt, 0, sizeof(cnt));
	for (int i = 0; i < n; i++)
		g[i].clear();
	while (!q.empty())
		q.pop();
	int s;
	cin >> n >> m >> s;
	for (int i = 0; i < m; i++)
	{
		int u, v, w;
		cin >> u >> v >> w;
		u--, v--;
		g[u].push_back({v, w});
	}
	if (SPFA(s - 1))
	{
		for (int i = 0; i < n; i++)
			cout << dist[i] << endl;
	}
	else
		cout << "-1" << endl;
	return 0;
}

L - 做题做累了?点我玩个游戏放松一下8

在这里插入图片描述
binary tree,应该是下一讲的预习。对一棵二叉树,两人轮流删除,一次只能删除一个完全二叉树,删除完的人胜。对完全二叉树,一次删一个节点,因为是奇数个节点,所以必定是先动手的人赢;进一步推论对完全二叉树,一次删除一课完全二叉子树也是一样,先动手的赢;所以所有完全二叉子树都可以化简为一个节点,那么对任何一棵二叉树,节点数为奇数则先动手的赢,偶数则后动手的赢。

#include <iostream>

int main()
{
	int kase;
	cin >> kase;
	while (kase--)
	{
		int n;
		cin >> n;
		if (n % 2 == 0)
			cout << "Bob" << endl;
		else
			cout << "Alice" << endl;
		int x, y;
		for (int i = 0; i < n - 1; i++)
			cin >> x >> y;
	}

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值