「ABC 218」思路
图论专场?
前言
A、B
略。
C
考场上用了 20min A了这道题,其中 12min 都在理解题意,我在线谢谢出题人我的英语。/wx
转 \(4\) 次。每次判断两矩形是否平移后相同即可。
判断也很简单,分别将两个矩形的左上角靠在 \((1,1)\),看看他们是不是完全一样。
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define uint unsigned int
#define LL long long
using namespace std;
const int MAXN = 405;
int n, minx, miny, minxx, minyy;
char s1[MAXN][MAXN], s2[MAXN][MAXN], qwq[MAXN][MAXN];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%s", s1[i] + 1);
for(int i = 1; i <= n; i ++) scanf("%s", s2[i] + 1);
for(int k = 1; k <= 4; k ++) {
minx = 0x3f3f3f3f; miny = minxx = minyy = 0x3f3f3f3f;
int tmp = 0;
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j ++) if(s1[i][j] == '#') minx = min(minx, i), miny = min(miny, j), tmp ++;
}
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j ++) if(s2[i][j] == '#') minxx = min(minxx, i), minyy = min(minyy, j), tmp --;
}
if(!tmp) {
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j ++) {
if((s1[i + minx - 1][j + miny - 1] == '#') ^ (s2[i + minxx - 1][j + minyy - 1] == '#')) tmp = -1;
}
}
if(tmp != -1) { printf("Yes"); return 0; }
}
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j ++) qwq[j][n - i + 1] = s1[i][j];
}
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j ++) s1[i][j] = qwq[i][j];
}
}
printf("No"); return 0;
return 0;
}
D
此题大概率是固定一维,另一维用数据结构或其他算法求。
令这个矩形的左上角为 \((x_1,y_1)\),右上角为 \((x_2,y_2)\)。
枚举 \(y_1\)、\(y_2\),看有多少个 \(x\) 满足 \((x,y_1)\),\((x,y_2)\) 都存在,记其为 \(cnt_{y_1,y_2}\),聪明的读者能一眼看出答案即为 \(\sum \frac {cnt_{y_1,y_2}\times (cnt_{y_1,y_2}-1)} {2}\)。
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define uint unsigned int
#define LL long long
using namespace std;
const int MAXN = 4005;
struct Node {
int X, Y;
}arr[MAXN];
int n, lsh[MAXN], tot, X[MAXN], Y[MAXN], c[MAXN], dp[MAXN][MAXN];
LL pre[MAXN][MAXN];
bool vis[MAXN][MAXN];
LL ans;
bool cmp1(Node x, Node y) { return x.Y < y.Y; }
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d%d", &arr[i].X, &arr[i].Y), lsh[++ tot] = arr[i].X, lsh[++ tot] = arr[i].Y;
sort(lsh + 1, lsh + 1 + tot); tot = unique(lsh + 1, lsh + 1 +tot) - lsh - 1;
for(int i = 1; i <= n; i ++) arr[i].X = lower_bound(lsh + 1, lsh + 1 + tot, arr[i].X) - lsh, arr[i].Y = lower_bound(lsh +1, lsh +1 + tot, arr[i].Y) - lsh, vis[arr[i].X][arr[i].Y] = 1;
sort(arr + 1, arr + 1 + n, cmp1);
for(int i = 1; i <= n; i ++) {
for(int j = i + 1; j <= n; j ++) {
if(arr[i].X == arr[j].X) dp[arr[i].Y][arr[j].Y] ++;
}
}
for(int i = 1; i <= tot; i ++) for(int j = 1; j <= tot; j ++) ans += (LL)dp[i][j] * (dp[i][j] - 1) / 2;
printf("%lld", ans);
return 0;
}
E
最小生成树板题。
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define uint unsigned int
#define LL long long
using namespace std;
const int MAXN = 2e5 + 5;
struct Node {
int X, Y, Z;
bool operator < (const Node P) const { return Z < P.Z; }
}arr[MAXN];
int n, m, fa[MAXN];
LL ans;
int Find_Set(int x) { return fa[x] != x ? fa[x] = Find_Set(fa[x]) : fa[x]; }
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) fa[i] = i;
for(int i = 1; i <= m; i ++) scanf("%d%d%d", &arr[i].X, &arr[i].Y, &arr[i].Z), ans += arr[i].Z;
sort(arr + 1, arr + 1 + m);
for(int i = 1; i <= m; i ++) {
if(arr[i].Z <= 0) {
ans -= arr[i].Z;
int u = Find_Set(arr[i].X), v = Find_Set(arr[i].Y); if(u != v) fa[u] = v;
continue;
}
int u = Find_Set(arr[i].X), v = Find_Set(arr[i].Y);
if(u != v) fa[u] = v, ans -= arr[i].Z;
}
printf("%lld", ans);
return 0;
}
F
降智了。。。我是 sb。。。
不妨找出其不删任何边时的最短路(一条即可),发现只有删去在这路径上的边才有可能改变最短路的长度。
由于这条路上最多只有 \(n-1\) 条边,每次暴力跑,时间复杂度:\(\mathcal {O}(n(n+m))\)。
就这?考场上想到了先找出最短路,然后不知道在想什么立马把这个做法丢掉了。/qiang
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int MAXN = 405, MAXM = 405 * 405;
int n, m, tot, dp[MAXN], las[MAXN], id[MAXN], e[MAXN], res;
queue <int> que;
int Head[MAXN], Ver[MAXM], Next[MAXM], ans[MAXM];
void add(int x, int y) { Ver[++ tot] = y; Next[tot] = Head[x]; Head[x] = tot; }
int Calc(int x, int f) {
memset(dp, 0x3f, sizeof(dp)); dp[1] = 0;
que.push(1);
while(!que.empty()) {
int t = que.front(); que.pop();
for(int i = Head[t]; i; i = Next[i]) {
if(i == x || dp[Ver[i]] != 0x3f3f3f3f) continue;
int y = Ver[i];
if(dp[y] > dp[t] + 1) {
dp[y] = dp[t] + 1;
if(f) las[y] = t, e[y] = i;
}
que.push(y);
}
}
return dp[n] != 0x3f3f3f3f ? dp[n] : -1;
}
int main() {
int x, y;
scanf("%d%d", &n, &m); memset(dp, 0x3f, sizeof(dp));
for(int i = 1; i <= m; i ++) scanf("%d%d", &x, &y), add(x, y);
res = Calc(-1, 1);
for(int i = n; i; i = las[i]) if(e[i]) ans[e[i]] = Calc(e[i], 0);
for(int i = 1; i <= m; i ++) printf("%d\n", ans[i] ? ans[i] : res);
return 0;
}
G
其实不难想,只是考场降智了浪费了 20min,最后压线才做出来。/kk
令 \(max_x\) 为当前这个人站在 \(x\),决定下一步走哪里,由叶子走过来能得到的最大中位数,\(min_x\) 反之,易知这是一个树形 \(dp\)。
有些人可能像我刚开始一样,直接 \(max_x=\max (min_{son[x]})\),但是这样会有一个问题。就比如:\(\{2,2\}+\{6\}\),中位数是 \(2\)。\(\{2,2\}+\{4,6\}\),中位数却是 \(3\)。也就是说,由子节点中位数最大/最小的一个转移过来,并不一定是最优的。此做法在于我们不能动态地知道怎么选最优秀。(废话)
既然不能动态地知道,那就先求出来。
所以我们可以逆向思考,从 \(1\) 到所有叶子结点 \(x\) 形成的中位数我们可以 \(\mathcal {O}(nlog_2(n))\) 求出(线段树等),用这个中位数来转移就没错了。
(由于考场上先打的错误的做法,这里的线段树没时间改了,我用的权值线段树)
(请不要在意函数名,虽然我不说大家可能都发现不了)
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#define uint unsigned int
#define LL long long
#define ls tree[p].L
#define rs tree[p].R
using namespace std;
const int MAXN = 1e5 + 5, inf = 1e9;
struct Smt {
int L, R, Val;
}tree[MAXN * 40];
int n, a[MAXN], root[MAXN], cnt = 1, dp[MAXN];
vector <int> v[MAXN];
int add(int p, int l, int r, int x, int v) {
if(!p) p = ++ cnt;
if(l == r) { tree[p].Val += v; return p; }
int mid = (l + r) >> 1;
if(x <= mid) ls = add(ls, l, mid, x, v);
else rs = add(rs, mid + 1, r, x, v);
tree[p].Val = tree[ls].Val + tree[rs].Val; return p;
}
int Getkth(int p, int l, int r, int k) {
if(l == r) return l;
int mid = (l + r) >> 1;
if(tree[ls].Val >= k) return Getkth(ls, l, mid, k);
return Getkth(rs, mid + 1, r, k - tree[ls].Val);
}
void Shit(int x, int fa) {
int t = 0; add(1, 2, inf, a[x], 1);
for(uint i = 0; i < v[x].size(); i ++) {
int y = v[x][i]; if(y == fa) continue;
Shit(y, x); t ++;
}
if(t == 0) {
if(tree[1].Val & 1) dp[x] = Getkth(1, 2, inf, tree[1].Val / 2 + 1);
else dp[x] = (Getkth(1, 2, inf, tree[1].Val / 2 + 1) + Getkth(1, 2, inf, tree[1].Val / 2)) / 2;
}
add(1, 2, inf, a[x], -1);
}
void dfs(int x, int fa, int f) {
if(f & 1) {
for(uint i = 0; i < v[x].size(); i ++) {
int y = v[x][i]; if(y == fa) continue;
dfs(y, x, f ^ 1); dp[x] = max(dp[x], dp[y]);
}
}
else {
int t = 0x3f3f3f3f;
for(uint i = 0; i < v[x].size(); i ++) {
int y = v[x][i]; if(y == fa) continue;
dfs(y, x, f ^ 1); t = min(t, dp[y]);
}
if(t != 0x3f3f3f3f) dp[x] = t;
}
}
int main() {
int x, y;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for(int i = 1; i < n; i ++) {
scanf("%d%d", &x, &y); v[x].push_back(y); v[y].push_back(x);
}
Shit(1, -1); dfs(1, -1, 1); printf("%d", dp[1]);
return 0;
}
H
咕咕咕了。。。等会做。。。
(写得不好,反正大家看着玩玩就行啦,,)
正题
我是sb。