AtCoder Beginner Contest 218

A. 简单判断

根据题义:

  • S [ N ] = x S[N] = x S[N]=x,输出 N o No No
  • S [ N ] = o S[N] = o S[N]=o,输出 Y e s Yes Yes
#include<bits/stdc++.h>
using namespace std;

int main(void) {
	string s;
	int n;
	cin >> n >> s;
	if (s[n - 1] == 'x') cout << "No\n";
	else cout << "Yes\n";
	return 0;
}

B. 字符映射

由题义,你需要构造 26 26 26 个字母的一个排列使得其相对大小和给定的 26 26 26 个整数一样

很显然只需要构造在原先整数的基础上加上一个偏移量即可保证相对大小满足条件

1 → a , 2 → b , . . . , 26 → z 1\rightarrow a, 2\rightarrow b, ..., 26 \rightarrow z 1a,2b,...,26z

#include<bits/stdc++.h>
using namespace std;


int main(void) {
	int x;
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	for (int i = 0; i < 26; i++) {
		cin >> x;
		cout << (char) ('a' + x - 1);
	}
	cout << '\n';
	return 0;
}

C. 旋转平移同构判断

首先我们先考虑旋转操作,因为一次只能旋转 90 90 90 度,所以只需要旋转四次,每次判断是否同构即可

现在问题变为:对于两个图像,如何判断其是否通过平移来使其重合呢?

下面给出一种比较方便操作的方法:

  • 从上往下,从左往右扫矩阵,将其为 # 字符的位置全部记录下来
  • S , T S, T S,T 进行如上操作,得到两个序列 P S , P T P_S, P_T PS,PT
  • 判断是否可以通过一个偏移量,使得 P S P_S PS 变为 P T P_T PT 即可,显然若可以,这个偏移量只可能是其中第一个点的横总坐标之差

关键点在于,采用同一种遍历方式得到的 P S , P T P_S, P_T PS,PT,如果存在一个偏移量 d d d 使得 P S + d = P T P_S + d = P_T PS+d=PT,就意味着 S , T S, T S,T 同构

#include<bits/stdc++.h>
using namespace std;

const int N = 2e2 + 5;
char s[N][N], t[N][N], tmp[N][N];
int n;

void rotate(char mat[N][N]) {
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			tmp[j][n - i + 1] = mat[i][j];
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			mat[i][j] = tmp[i][j];
		}
	}
}

bool cmp(char a[N][N], char b[N][N]) {
	vector<pair<int, int>> p1, p2;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			if (a[i][j] == '#') p1.push_back({i, j});
			if (b[i][j] == '#') p2.push_back({i, j});
		}
	}
	
	if (p1.size() != p2.size()) return false;
	int dx = p1[0].first - p2[0].first, dy = p1[0].second - p2[0].second;
	for (int i = 0; i < (int) p1.size(); i++) {
		if (p1[i].first - p2[i].first != dx || p1[i].second - p2[i].second != dy) return false;
	}
	return true;
}

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> s[i] + 1;
	}
	for (int i = 1; i <= n; i++) {
		cin >> t[i] + 1;
	}
	for (int i = 0; i < 4; i++) {
		if (cmp(s, t)) {
			cout << "Yes\n";
			return 0;
		}
		rotate(s);
	}
	cout << "No\n";
	return 0;
}

D. 枚举

首先思考这样的一个问题:什么条件确定一个矩形,这是解决此问题的关键

四个点确定一个矩形,但是如果去暴力地枚举四个点,显然会 T L E TLE TLE

一条对角线确定一个矩形,我们只需要枚举每条对角线的两个端点即可

对于两点 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1, y_1), (x_2, y_2) (x1,y1),(x2,y2),其成为一个矩形对角线的条件是:

  1. x 1 ≠ x 2 , y 1 ≠ y 2 x_1 \neq x_2, y_1\neq y_2 x1=x2,y1=y2
  2. 存在另外两个点 ( x 1 , y 2 ) , ( x 2 , y 1 ) (x_1, y_2), (x_2, y_1) (x1,y2),(x2,y1)

对于 1 1 1 条件,我们可以 O ( 1 ) O(1) O(1) 判断

对于 2 2 2 条件,我们可以用一个 s e t set set 存储所有的点坐标,可以实现 O ( log ⁡ N ) O(\log N) O(logN) 查询

同时,需要注意的是,每个矩形会被其两条角线取两次,所以统计的答案还需除以 2 2 2

总时间复杂度: O ( N 2 log ⁡ N ) O(N^2\log N) O(N2logN)

#include<bits/stdc++.h>
using namespace std;

int n;
set<pair<int, int>> st;
vector<pair<int, int>> p;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n;
	for (int i = 1, x, y; i <= n; i++) {
		cin >> x >> y;
		st.insert({x, y});
		p.push_back({x, y});
	}
	int ans = 0;
	for (int i = 0; i < n; i++) {
		for (int j = i + 1; j < n; j++) {
			if (p[i].first == p[j].first || p[i].second == p[j].second) continue;
			ans += st.count({p[i].first, p[j].second}) && st.count({p[j].first, p[i].second});
		}
	}
	cout << ans / 2 << '\n';
	return 0;
}

E. 最小生成树

题义:给定一个无向连通图,求在保证图连通的情况下,删去的边的权值和的最大值

首先注意一个条件: − 1 0 9 ≤ C i ≤ 1 0 9 -10^9 \leq C_i\leq 10^9 109Ci109

我们先考虑: C i ≥ 0 C_i \geq 0 Ci0 的情况下,怎么做?

其实很容易看出这是一个最小生成树的题,因为所有边的权指和一定,要使得删去的边的权指最大,意味着剩下的点的权值和最小,这就等同于最小生成树问题,跑一遍最小生成树即可

C i < 0 C_i < 0 Ci<0 的情况下,不删一定比删更好,且不删一定不会破坏连通性,所以对于 C i < 0 C_i < 0 Ci<0 的边保留即可

时间复杂度: O ( M log ⁡ N ) O(M\log N) O(MlogN)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5;

struct node {
	int u, v, w;
};
vector<node> edge;

int fa[N];
struct disjointset {
	disjointset(int n) {
		for (int i = 1; i <= n; i++) {
			fa[i] = i;
		}
	}
	int find(int x) {
		return fa[x] == x? fa[x]: fa[x] = find(fa[x]);
	}
};

bool cmp(node a, node b) {
	return a.w < b.w;
}

int main(void) {
	int n, m;
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m;
	edge.resize(m);
	for (int i = 0; i < m; i++) {
		cin >> edge[i].u >> edge[i].v >> edge[i].w;
	}
	sort(edge.begin(), edge.end(), cmp);
	disjointset v(n);
	ll res = 0;
	for (int i = 0; i < m; i++) {
		int urt = v.find(edge[i].u), vrt = v.find(edge[i].v);
		if (urt == vrt) {
			res += max(0, edge[i].w);
			continue;
		}
		fa[urt] = vrt;
	}
	cout << res << '\n';
	return 0;
}

F. 去边最短路

首先很容易想到的一个思路是:枚举去掉的边,每次都跑一遍最短路,肯定是 T L E TLE TLE

那么接着想到,对于不删边的最短路来说,该最短路记为 p a t h path path

  • p a t h path path 一定不会经过一个点两次,所以 p a t h path path 上经过的节点个数 ≤ N \leq N N,边数 ≤ N − 1 \leq N - 1 N1
  • 如果删除的边不是最短路上的边,那么最短路径的长度不会变
  • 如果删除的边是最短路上的边,暴力跑一边最短路即可

最短路上的边数是 O ( N ) O(N) O(N) 的,那么现在考虑在上述第三种情况下怎么跑最短路

  • 如果是堆优化的 d i j k s t r a dijkstra dijkstra 最短路,其时间复杂度是 O ( M log ⁡ N ) O(M\log N) O(MlogN) 的,总时间复杂度为 O ( N M log ⁡ N ) O(NM\log N) O(NMlogN),在 M = N ( N − 1 ) M = N(N - 1) M=N(N1) 的情况下变为 O ( N 3 log ⁡ N ) O(N^3\log N) O(N3logN),目测是会 T L E TLE TLE
  • 如果是基础的 d i j k s t a dijksta dijksta 最短路,其时间复杂度为 O ( N 2 ) O(N^2) O(N2),总时间复杂度为 O ( N 3 ) O(N^3) O(N3),更优
  • 通过这个例子可以看出,选算法要看具体的应用场景,堆优化的最短路未必比基础版的好
  • 实际上,堆优化的最短路适用与稀疏图,而未经堆优化的最短路在稠密图上的表现要更好

时间复杂度: O ( N 3 ) O(N^3) O(N3)

#include<bits/stdc++.h>
using namespace std;

const int N = 4e2 + 5, inf = 0x3f3f3f3f;
int w[N][N], d[N], vis[N], pre[N], ans[N][N], n, m;

int solve(void) {
	fill(d + 1, d + n + 1, inf);
	fill(vis + 1, vis + n + 1, 0);
	fill(pre + 1, pre + n + 1, 0);
	d[1] = 0;
	for (int i = 1; i <= n; i++) {
		int mnd = inf, now = -1;
		for (int j = 1; j <= n; j++) {
			if (!vis[j] && d[j] < mnd) {
				mnd = d[j], now = j;
			}
		}
		if (now == -1) break;
		vis[now] = 1;
		for (int j = 1; j <= n; j++) {
			if (d[now] + w[now][j] < d[j]) {
				d[j] = d[now] + w[now][j];
				pre[j] = now;
			}
		}
	}
	return d[n] == inf? -1: d[n];
}

vector<pair<int, int>> edge;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			w[i][j] = inf;
		}
	}
	for (int i = 1, s, t; i <= m; i++) {
		cin >> s >> t;
		w[s][t] = 1;
		edge.push_back({s, t});
	}
	vector<int> path;
	int v = solve();
	int now = n;
	while (now) {
		path.push_back(now);
		now = pre[now];
	}
	reverse(path.begin(), path.end());
	for (int i = 1; i < (int) path.size(); i++) {
		int s = path[i - 1], t = path[i];
		int tmp = w[s][t];
		w[s][t] = inf;
		ans[s][t] = solve();
		w[s][t] = tmp;
	}
	for (int i = 0; i < m; i++) {
		if (ans[edge[i].first][edge[i].second]) {
			cout << ans[edge[i].first][edge[i].second] << '\n';
		}
		else cout << v << '\n';
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值