(mathsf{7.16})
(mathcal{Problem})
T1: 给出两个矩形,问是否可以将一个矩形放在另一个矩形的内部(含边界),多
测。
T2: 有向图,询问两点间最短路,有删边操作,(n leq 200,) 删边操作 (leq 200) 次。
T3: 给出一个 n 个点的树,在两个合适且不同的点放上奖杯,使得每个点到最近
的奖杯距离最大值最小。
(mathcal{During})
没怎么好好考, 最后甚至没交,是补交的。
- T1一开始没反应过来,以为不是随便比较一下长宽就好了嘛。
然后中间发现(听到)可以旋转,人傻了。
公式到考试结束还没推出来。
- T2一看弗洛伊德板子,然后发现复杂度每次重新做的话不太对。但也没办法了,直接交了。
时间复杂度瓶颈在于每次删边都重新做一次最短路,。
瓶颈时间复杂度(mathrm{O(n(删边操作) imes n^3(Floyd))} = mathrm{O(n^4)}), 50分。
我知道这样不优,但是想不到更好的方法。
- T3二分答案验证写到一半发现T1不对劲,去写T1了。
(mathcal{After})
(mathbb{Solution})
T1
我们设宽小的矩形宽 a、 长 b, 宽大的矩形宽 A 、 长 B。
- 注意输入的矩形,换一下再得到以上顺序(a b A B)
能塞下矩形的条件是
问题就是存不存在一个这样的 (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;
}
然后记录我当时的推导,不知为何最后错了,哪位大佬路过还望指点。
一开始我的推导过程如下:
由此可知 (varphi) 与 (eta) 互补,又有 (sin (alpha + eta) = sin(alpha + dfrac{pi}{2} - varphi) = sin(dfrac{pi}{2} - (varphi - alpha)) = cos(varphi - alpha))
所以
其中 (varphi in [0, dfrac{pi}{2}], alpha in [0, dfrac{pi}{2}]) ,所以根据三角函数线
或
Tip: 数学题有时候难以推得正解公式,可以考虑稍微暴力一点的枚举。
T2
一道最短路。
正解为 离线询问 + Floyd 加边, 其中 Floyd 加边只需 (mathrm{O(n ^ 2)}) 。
具体操作:把所有询问离线,删边等于倒序加边,先对最终图跑一遍 Floyd,然后一边加边,一边处理询问。
瓶颈时间复杂度(mathrm{O(n(删边操作) imes 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是必须掌握的知识点,联赛天天树数数, 数数树。