• 【20200716】提高模拟赛


    (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)

    能塞下矩形的条件是

    [a cos alpha + b sin alpha leq B AndAnd 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;
    }
    

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

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

    [egin{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 + eta)[ sin eta = dfrac{b}{sqrt{a^2 + b^2}}, cos alpha = dfrac{a}{sqrt{a^2 + b^2}} ] end{aligned} ]

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

    所以

    [egin{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(删边操作) 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是必须掌握的知识点,联赛天天树数数, 数数树。


  • 相关阅读:
    函数指针Demo
    设计模式笔记
    Simple Factory 模式
    百度API操作实例
    如何发邮件不被认定为垃圾邮件的技巧
    DateTimePicker中自定义时间或日期显示格式
    取得指定月份的天数
    DataGridView中的内容太长时换行显示
    Numericupdown控件value值的非空判断
    C#中用SQL语句CreatTable
  • 原文地址:https://www.cnblogs.com/yywxdgy/p/13603728.html
Copyright © 2020-2023  润新知