A. 简单判断
根据题义:
- S[N]=xS[N] = xS[N]=x,输出 NoNoNo
- S[N]=oS[N] = oS[N]=o,输出 YesYesYes
#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. 字符映射
由题义,你需要构造 262626 个字母的一个排列使得其相对大小和给定的 262626 个整数一样
很显然只需要构造在原先整数的基础上加上一个偏移量即可保证相对大小满足条件
即 1→a,2→b,...,26→z1\rightarrow a, 2\rightarrow b, ..., 26 \rightarrow z1→a,2→b,...,26→z
#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. 旋转平移同构判断
首先我们先考虑旋转操作,因为一次只能旋转 909090 度,所以只需要旋转四次,每次判断是否同构即可
现在问题变为:对于两个图像,如何判断其是否通过平移来使其重合呢?
下面给出一种比较方便操作的方法:
- 从上往下,从左往右扫矩阵,将其为 # 字符的位置全部记录下来
- 对 S,TS, TS,T 进行如上操作,得到两个序列 PS,PTP_S, P_TPS,PT
- 判断是否可以通过一个偏移量,使得 PSP_SPS 变为 PTP_TPT 即可,显然若可以,这个偏移量只可能是其中第一个点的横总坐标之差
关键点在于,采用同一种遍历方式得到的 PS,PTP_S, P_TPS,PT,如果存在一个偏移量 ddd 使得 PS+d=PTP_S + d = P_TPS+d=PT,就意味着 S,TS, TS,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. 枚举
首先思考这样的一个问题:什么条件确定一个矩形,这是解决此问题的关键
四个点确定一个矩形,但是如果去暴力地枚举四个点,显然会 TLETLETLE
一条对角线确定一个矩形,我们只需要枚举每条对角线的两个端点即可
对于两点 (x1,y1),(x2,y2)(x_1, y_1), (x_2, y_2)(x1,y1),(x2,y2),其成为一个矩形对角线的条件是:
- x1≠x2,y1≠y2x_1 \neq x_2, y_1\neq y_2x1=x2,y1=y2
- 存在另外两个点 (x1,y2),(x2,y1)(x_1, y_2), (x_2, y_1)(x1,y2),(x2,y1)
对于 111 条件,我们可以 O(1)O(1)O(1) 判断
对于 222 条件,我们可以用一个 setsetset 存储所有的点坐标,可以实现 O(logN)O(\log N)O(logN) 查询
同时,需要注意的是,每个矩形会被其两条角线取两次,所以统计的答案还需除以 222
总时间复杂度:O(N2logN)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. 最小生成树
题义:给定一个无向连通图,求在保证图连通的情况下,删去的边的权值和的最大值
首先注意一个条件:−109≤Ci≤109-10^9 \leq C_i\leq 10^9−109≤Ci≤109
我们先考虑:Ci≥0C_i \geq 0Ci≥0 的情况下,怎么做?
其实很容易看出这是一个最小生成树的题,因为所有边的权指和一定,要使得删去的边的权指最大,意味着剩下的点的权值和最小,这就等同于最小生成树问题,跑一遍最小生成树即可
当 Ci<0C_i < 0Ci<0 的情况下,不删一定比删更好,且不删一定不会破坏连通性,所以对于 Ci<0C_i < 0Ci<0 的边保留即可
时间复杂度:O(MlogN)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. 去边最短路
首先很容易想到的一个思路是:枚举去掉的边,每次都跑一遍最短路,肯定是 TLETLETLE 的
那么接着想到,对于不删边的最短路来说,该最短路记为 pathpathpath
- pathpathpath 一定不会经过一个点两次,所以 pathpathpath 上经过的节点个数 ≤N\leq N≤N,边数 ≤N−1\leq N - 1≤N−1
- 如果删除的边不是最短路上的边,那么最短路径的长度不会变
- 如果删除的边是最短路上的边,暴力跑一边最短路即可
最短路上的边数是 O(N)O(N)O(N) 的,那么现在考虑在上述第三种情况下怎么跑最短路
- 如果是堆优化的 dijkstradijkstradijkstra 最短路,其时间复杂度是 O(MlogN)O(M\log N)O(MlogN) 的,总时间复杂度为 O(NMlogN)O(NM\log N)O(NMlogN),在 M=N(N−1)M = N(N - 1)M=N(N−1) 的情况下变为 O(N3logN)O(N^3\log N)O(N3logN),目测是会 TLETLETLE 的
- 如果是基础的 dijkstadijkstadijksta 最短路,其时间复杂度为 O(N2)O(N^2)O(N2),总时间复杂度为 O(N3)O(N^3)O(N3),更优
- 通过这个例子可以看出,选算法要看具体的应用场景,堆优化的最短路未必比基础版的好
- 实际上,堆优化的最短路适用与稀疏图,而未经堆优化的最短路在稠密图上的表现要更好
时间复杂度:O(N3)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;
}