【20200716】提高模拟赛

\(\mathsf{7.16}\)

\(\mathcal{Problem}\)

T1: 给出两个矩形,问是否可以将一个矩形放在另一个矩形的内部(含边界),多
测。

T2: 有向图,询问两点间最短路,有删边操作,\(n \leq 200,\) 删边操作 \(\leq 200\) 次。

T3: 给出一个 n 个点的树,在两个合适且不同的点放上奖杯,使得每个点到最近
的奖杯距离最大值最小。

\(\mathcal{During}\)

没怎么好好考, 最后甚至没交,是补交的。

  • T1一开始没反应过来,以为不是随便比较一下长宽就好了嘛。

然后中间发现(听到)可以旋转,人傻了。

公式到考试结束还没推出来。

  • T2一看弗洛伊德板子,然后发现复杂度每次重新做的话不太对。但也没办法了,直接交了。

时间复杂度瓶颈在于每次删边都重新做一次最短路,。

瓶颈时间复杂度\(\mathrm{O(n(删边操作)\times n^3(Floyd))} = \mathrm{O(n^4)}\), 50分。

我知道这样不优,但是想不到更好的方法。

  • T3二分答案验证写到一半发现T1不对劲,去写T1了。

\(\mathcal{After}\)

\(\mathbb{Solution}\)

T1

我们设宽小的矩形宽 a、 长 b, 宽大的矩形宽 A 、 长 B。

  • 注意输入的矩形,换一下再得到以上顺序(a b A B)

能塞下矩形的条件是

\[a \cos \alpha + b \sin \alpha \leq B \ \And\And \ a \sin \alpha + b \cos \alpha \leq A \]

问题就是存不存在一个这样的 \(\alpha\) 能满足条件。

那么为了A掉这道题,我们可以枚举它。你每 0.01 度检验一次, 几乎不可能出现错误,事实上我觉得再往上几十位不是问题。

\(\mathrm{Code:}\)

#include <bits/stdc++.h>
const double Pi = acos(-1);
int n;
double a1, b1, a2, b2;
inline int read() {
    int s = 0, w = 1;
    char c = getchar();
    while ((c < '0' || c > '9') && c != '-') c = getchar();
    if (c == '-') w = -1, c = getchar();
    while (c <= '9' && c >= '0') s = (s << 3) + (s << 1) + c - '0', c = getchar();
    return s * w;
}
signed main() {
    freopen("girls.in", "r", stdin);
    freopen("girls.out", "w", stdout);
    n = read();
    for (; n; --n) {
        a1 = read(), b1 = read(), a2 = read(), b2 = read();
        if ((a1 <= a2 && b1 <= b2) || (a2 <= a1 && b2 <= b1)) {
            puts("Yes");
            continue;
        }
        double a = std ::min(a1, b1), b = std ::max(a1, b1);
        double A = std ::min(a2, b2), B = std ::max(a2, b2);
        if (a > A) std ::swap(a, A), std ::swap(b, B);
        bool p = 0;
        for (double arf = 0; arf <= 90; arf += 0.01) {
            double kk = arf * Pi / 180;
            if (a * cos(kk) + b * sin(kk) <= B && a * sin(kk) + b * cos(kk) <= A) {
                puts("Yes"), p = 1;
                break;
            }
        }
        if (!p) puts("No");
    }
    return 0;
}

然后记录我当时的推导,不知为何最后错了,哪位大佬路过还望指点。

一开始我的推导过程如下:

\[\begin{aligned} & a \cos \alpha + b \sin \alpha = \sqrt{a^2 + b^2}\sin (\alpha + \varphi) [\ \sin \varphi = \dfrac{a}{\sqrt{a^2 +b^2}}, \cos \varphi = \dfrac{b}{\sqrt{a^2 + b^2}}\ ]\\ & a \sin \alpha + b \cos \alpha = \sqrt{a^2 + b^2}\sin(\alpha + \beta)[\ \sin \beta = \dfrac{b}{\sqrt{a^2 + b^2}}, \cos \alpha = \dfrac{a}{\sqrt{a^2 + b^2}}\ ] \end{aligned} \]

由此可知 \(\varphi\)\(\beta\) 互补,又有 \(\sin (\alpha + \beta) = \sin(\alpha + \dfrac{\pi}{2} - \varphi) = \sin(\dfrac{\pi}{2} - (\varphi - \alpha)) = \cos(\varphi - \alpha)\)

所以

\[\begin{aligned} & a \cos \alpha + b \sin \alpha = \sqrt{a^2 + b^2}\sin (\varphi + \alpha) \leq B\\ & a \sin \alpha + b \cos \alpha = \sqrt{a^2 + b^2}\cos(\varphi - \alpha) \leq A \end{aligned} \]

其中 \(\varphi \in [0, \dfrac{\pi}{2}], \alpha \in [0, \dfrac{\pi}{2}]\) ,所以根据三角函数线

\[\varphi + \alpha \geq \arcsin(\dfrac{B}{\sqrt{a^2 + b^2}}) , \varphi - \alpha \geq \arccos(\dfrac{A}{\sqrt{a^2 + b^2}}) \\ \iff 2 \arcsin(\dfrac{a}{\sqrt{a^2 + b^2}}) \geq \arcsin(\dfrac{B}{\sqrt{a^2 + b^2}}) + \arccos(\dfrac{A}{\sqrt{a^2 + b^2}}) \]

\[\varphi + \alpha \leq \arcsin(\dfrac{B}{\sqrt{a^2 + b^2}}) , \varphi - \alpha \geq \arccos(\dfrac{A}{\sqrt{a^2 + b^2}}) \\ \iff \min(\arcsin(\dfrac{B}{\sqrt{a^2 + b^2}}), \arccos(\dfrac{A}{\sqrt{a^2 + b^2}})) \geq 0 \]

Tip: 数学题有时候难以推得正解公式,可以考虑稍微暴力一点的枚举。


T2

一道最短路。

正解为 离线询问 + Floyd 加边, 其中 Floyd 加边只需 \(\mathrm{O(n ^ 2)}\)

具体操作:把所有询问离线,删边等于倒序加边,先对最终图跑一遍 Floyd,然后一边加边,一边处理询问

瓶颈时间复杂度\(\mathrm{O(n(删边操作)\times n^2 (Floyd加点))} = \mathrm{O(n^3)}\)

\(\mathrm{Code:}\)

#include <bits/stdc++.h>
#define int long long
#define FOR(i, a, b) for (int i = (a), bb = (b); i <= bb; ++i)
#define DOWN(i, a, b) for (int i = (a), bb = (b); i >= bb; --i)
const int N = 210, M = 1e5 + 10, EMAX = 1e9;
int n, m, a[N][N], f[N][N], cnt1 = 0, cnt2 = 0, ans[M];
struct Que { int t, x, y; } qu[M];
struct Operation { int t, x, y, val; } ope[N];
inline int read() {
    int s = 0, w = 1;
    char c = getchar();
    while ((c < '0' || c > '9') && c != '-') c = getchar();
    (c == '-') ? w = -1, c = getchar() : 0;
    while (c <= '9' && c >= '0') s = (s << 3) + (s << 1) + c - '0', c = getchar();
    return s * w;
}
template <class T>
inline void write(T x) {
    if (x < 0) x = ~x + 1, putchar('-');
    if (x > 9) write(x / 10);
    return putchar(x % 10 + 48), void();
}
inline void Floyd() {
    FOR(k, 1, n) FOR(i, 1, n) FOR(j, 1, n)
        if (i != j) f[i][j] = std ::min(f[i][j], f[i][k] + f[k][j]);
}			// 平凡的Floyd (8:00)   /xyx
inline void Add(int x, int y, int val) {
    FOR(i, 1, n) FOR(j, 1, n) 
    	if (i != j) f[i][j] = std ::min(f[i][j], f[i][x] + val + f[y][j]);
}			// Floyd加边
inline void Update(int &now, int i) {
    while (qu[now].t > ope[i].t) {
        int x = qu[now].x, y = qu[now].y;
        ans[now] = f[x][y], --now;
    }
}			// 处理离线下来的询问
signed main() {
    freopen("journey.in", "r", stdin);
    freopen("journey.out", "w", stdout);
    n = read(), m = read();
    FOR(i, 1, n) FOR(j, 1, n) f[i][j] = a[i][j] = read();
    FOR(i, 1, m) {
        int opt = read(), x = read(), y = read();
        if (opt == 1) ope[++cnt1] = (Operation) {i, x, y, a[x][y]}, a[x][y] = f[x][y] = EMAX;
        else qu[++cnt2] = (Que) { i, x, y };
    }		// 离线询问、操作,并记录时间以便处理询问,记录操作值val = a[x][y]
    Floyd();
    int now = cnt2;
    DOWN(i, cnt1, 1) Update(now, i), Add(ope[i].x, ope[i].y, ope[i].val);
    while (now) {
        int x = qu[now].x, y = qu[now].y;
        ans[now] = f[x][y], --now;
    }		// 最后还会留下一些没删边的询问,单独处理
    FOR(i, 1, cnt2) write(ans[i] < EMAX ? ans[i] : -1), i < cnt2 ? putchar(10) : 0;
    return 0;
}

下面记录调代码时候的一个小锅:

while (now) {
    int x = qu[now].x, y = qu[now].y;
    ans[now] = f[x][y], --now;
}

这段代码显然是处理询问操作, 那么对于当前的处理点 now, 一开始我没有 &, 出了什么事故大家应该都知道,就是函数中处理点 now 下移,而主程序中则并没有变化,卡了我好几次提交。

Tip: 逆向思维是神仙, 特别是最短路问题中,从终点出发、过程逆转都是常见套路。


T3

我们着手研究这两个点的性质, 两种方法:

  • 1.二分答案, 树形dp验证
  • 2.利用直径性质树形dp

我写的是第二种。

思考:如果只有一个点,奖杯放哪?

显然, 奖杯放在直径的中点,答案为直径/2。

那么两个点,我们就需要将它拆分成1个点的问题处理。

我们对每个点,如果它离第一个奖杯近,就标记为1, 如果离第二个奖杯近,就标记为2,如图:

值得注意的是:

  • 1、2一定会将树分为两个连通块, 所以必然有一条边左边是1、右边是2.

    这种情况下可能的答案为 两边子树的直径的一半的最大值 ,因为奖杯必然放在子树直径的中点。

然后还有一个性质,可以通过猜测观察发现:

  • 这样的点一定在直径上。

怎么想呢?我们找到这样的边,左边深度尽可能大,右边深度也尽可能得大,显然就是直径了,虽然比较抽象但是随便想象一下这样的树就好了。

接下来就好办了,找到直径并标记,树形DP找子树直径, 枚举直径上的边, 更新答案

#include <bits/stdc++.h>
#define FOR(i, a, b) for (int i = (a), bb = (b); i <= bb; ++i)
#define S_H(T, i, u) for (int i = T.fl[u]; i; i = T.net[i])
const int N = 2e5 + 10;
int n, x, y, l = 0, r = n, mid;
struct Tree {
	int to[N << 1], net[N << 1], fl[N], len;
	inline void Inc(int x, int y) 
		{ to[++len] = y, net[len] = fl[x], fl[x] = len; }
} T;
inline int read() {
	int s = 0, w = 1; char c = getchar();
	for (; !isdigit(c) && c != '-'; c = getchar());
	(c == '-') ? w = -1, c = getchar() : 0;
	for (; isdigit(c); c = getchar()) s = (s << 3) + (s << 1) + c - '0';
	return s * w;
}
template <class T>
inline void write(T x) {
	if (x < 0) x = ~x + 1, putchar('-');
	if (x > 9) write(x / 10);
	return putchar(x % 10 + 48), void();
}
int f[2][N], f1[2][N], g[2][N],dep[N], F[N], st, en;
inline void Dfs1(int u, int fa) {
	F[u] = fa, dep[u] = dep[fa] + 1;
	S_H(T, i, u) if (T.to[i] != fa) Dfs1(T.to[i], u);
}
inline void Dfs2(int u, int fa, int op) {
	S_H(T, i, u) {
		int v = T.to[i];
		if (v == fa) continue;
		Dfs2(v, u, op);
		if (f[op][u] < f[op][v] + 1) f1[op][u] = f[op][u], f[op][u] = f[op][v] + 1;
		else if (f1[op][u] < f[op][v] + 1) f1[op][u] = f[op][v] + 1;
		g[op][u] = std ::max(g[op][u], g[op][v]);
	}
	g[op][u] = std ::max(g[op][u], f[op][u] + f1[op][u]);
}
signed main() {
	freopen("ob.in", "r", stdin);
	freopen("ob.out", "w", stdout);
	n = read();
	FOR(i, 1, n - 1) x = read(), y = read(), T.Inc(x, y), T.Inc(y, x);

	Dfs1(1, 0); FOR(i, 1, n) if (dep[i] > dep[st]) st = i;
	Dfs1(st, 0); FOR(i, 1, n) if (dep[i] > dep[en]) en = i;

	Dfs2(st, 0, 0), Dfs2(en, 0, 1);
	int ans = (dep[en] + 1) / 2;
	for (int i = en; F[i]; i = F[i]) {
		int u = i, v = F[i], lu = g[0][u], lv = g[1][v];
		ans = std ::min(ans, (std ::max(lu, lv) + 1) / 2);
	}
	write(ans), putchar(10);
	return 0;
}

Tip: 以前做过的题都要有印象啊。。树形dp是必须掌握的知识点,联赛天天树数数, 数数树。


posted @ 2020-09-02 19:51  云烟万象但过眼  阅读(82)  评论(0编辑  收藏  举报