「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。

posted @ 2021-09-11 23:54  Saintex  阅读(70)  评论(0编辑  收藏  举报