• 20220609模拟赛 总结


    高峰期

    \(n\) 个点 \(m\) 条边的无向图,一条道路为 \((u,v,w,d)\)

    如果在时间 \(t\) 通过道路 \(i\) 则需要花费 \(c_i+\lfloor\dfrac{d_i}{t}\rfloor\) ,可以在任意城市停留整数单位的时间,

    求从 1 到 \(n\) 的最早时间,不能到达则输出 -1

    \(n\le 10^5\)

    改良的 dijkstra ,设 \(dis_u\) 为到 \(u\) 的最早时间,对于 \((u,v)\)

    若在 \(t(t\ge dis_u)\) 时刻出发去 \(v\)\(dis_v\) 可能被更新为 \(t+\lfloor\dfrac{d}{t}\rfloor+c_i\)

    其实是要最小化 \(t+1+\lfloor\dfrac{d}{t+1}\rfloor\)

    \(f(t)=t+\lfloor\dfrac{d}{t}\rfloor\) 这是一个单峰函数,存在最小值。暴力三分是会 TLE

    这是一个凹的函数,且在整数域上,找到第一个使得 \(f(t)\le f(t+1)\)\(t\) ,就能得到最小值

    \[\begin{aligned} f(t) & \le f(t+1)\\ f(t)-f(t+1)&\le 0\\ \lfloor\dfrac{d}{t}\rfloor-\lfloor\dfrac{d}{t+1}\rfloor&\le 1\\ \dfrac{d}{t}-\dfrac{d}{t+1} & \le 1\\ \dfrac{d}{t(t+1)}& \le 1\\ 0 & \le t^2+t-d \end{aligned} \]

    解得最小的 \(t=\lceil\dfrac{-1+\sqrt{4d+1}}{2}\rceil\) ,这个结果非常接近 \(\lfloor\sqrt{d}\rfloor\)

    只要在 \(\lfloor\sqrt{d}\rfloor\)\(\pm 1\) 中枚举取 \(\min\) 即可,前提是 \(t\ge dis_u\)

    #include <bits/stdc++.h>
    using namespace std;
    typedef unsigned long long uLL;
    typedef long double LD;
    typedef long long LL;
    typedef double db;
    const int N = 1e5 + 5;
    int n, m, lst[N], Ecnt = 1, vis[N];
    LL f[N];
    struct Ed { int to, nxt, qz, d; } e[N << 1];
    inline void Ae(int fr, int go, int vl, int k) {
        e[++Ecnt] = (Ed){ go, lst[fr], 1ll * vl, 1ll * k }, lst[fr] = Ecnt;
    }
    struct P {
        int x; LL d;
        bool operator < (P A) const {
            return d > A.d;
        }
    };
    priority_queue<P> Q;
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1, u, v, w, k; i <= m; i++) {
            scanf("%d%d%d%d", &u, &v, &w, &k);
            Ae(u, v, w, k), Ae(v, u, w, k);
        }
        for (int i = 1; i <= n; i++) f[i] = 1e18;
        f[1] = 0, Q.push((P){ 1, 0 });
        while (!Q.empty()) {
            int u = Q.top().x; Q.pop();
            if (vis[u]) continue;
            vis[u] = 1;
            for (int i = lst[u], v, d, sq; i; i = e[i].nxt) {
                v = e[i].to, d = e[i].d, sq = sqrt(d);
                LL qz = f[u] + d / (f[u] + 1);
                if (f[u] <= sq) qz = min(qz, 1ll * sq + d / (sq + 1));
                if (f[u] <= ++sq) qz = min(qz, 1ll * sq + d / (sq + 1));
                if (qz + e[i].qz < f[v]) f[v] = qz + e[i].qz, Q.push((P){ v, f[v] });
            }
        }
        if (f[n] == 1e18) puts("-1");
        else printf("%lld", f[n]);
    }
    

    榻榻米

    \(n\)\(m\) 列的棋盘,用 \(2\times 1\)\(1\times 1\) 的方块铺满,求方案数 \(\pmod {998244353}\)

    \(n\le 6,m\le 10^{12}\)

    一眼状压和矩阵乘法,设 \(1\) 为当前位置下放,\(0\) 为不下放

    初始化 \(f_{S,T}\) 为第一列状态 \(S\) 第二列状态为 \(T\) 的方案

    \(S\)\(T\) 不能同时在某一位为 \(1\) ,可以 dfs 暴力求出铺满剩下 \(0\) 的方案。

    注意 \(S\) 在某一位是 \(1\)\(T\) 的这一位其实是被覆盖了的

    \(f^{m-1}_{S,T}\) 就是第一列状态为 \(S\)\(m\) 列状态为 \(T\) 的方案。

    第一列状态任意,第 \(m\) 列必须为 0 ,求和即可

    #include <bits/stdc++.h>
    using namespace std;
    typedef unsigned long long uLL;
    typedef long double LD;
    typedef long long LL;
    typedef double db;
    const LL P = 998244353;
    int m, mx, ts[70];
    LL n;
    struct T {
        LL a[70][70];
        T() { memset(a, 0, sizeof(a)); }
        T operator * (T x) {
            T b;
            for (int i = 0; i <= mx; i++)
                for (int j = 0; j <= mx; j++)
                    for (int k = 0; k <= mx; k++)
                        (b.a[i][j] += a[i][k] * x.a[k][j] % P) %= P;
            return b;
        }
    } tmp, res;
    inline void Pow(LL n) {
        for (int i = 0; i <= mx; i++) res.a[i][i] = 1;
        for (; n; n >>= 1, tmp = tmp * tmp)
            if (n & 1) res = res * tmp;
    }
    int S, T, kk;
    inline int at(int x, int i) { return x & (1 << i); }
    inline bool chk() {
        kk = T;
        for (int i = 0; i < m; i++) {
            if (at(S, i) && at(T, i)) return 0;
            if (at(S, i)) kk |= 1 << i;
        }
        return 1;
    }
    int tt, ss;
    void dfs(int i) {
        if (i == m) { tt++; return; }
        if (i + 1 < m && !at(ss, i) && !at(ss, i + 1)) dfs(i + 2);
        dfs(i + 1);
    }
    int main() {
        scanf("%d%lld", &m, &n);
        mx = (1 << m) - 1;
        for (int i = 0; i <= mx; i++) {
            tt = 0, ss = i, dfs(0), ts[i] = tt;
        }
        for (S = 0; S <= mx; S++)
            for (T = 0; T <= mx; T++)
                if (chk()) tmp.a[S][T] = ts[kk];
        Pow(n - 1);
        LL ans = 0;
        for (int i = 0; i <= mx; i++) (ans += res.a[i][0] * ts[i]) %= P;
        printf("%lld", ans);
    }
    

    避难向导

    一棵 \(n\) 点的有边权的以 1 为根的树,定义 \(d_i\) 为点 \(i\) 到树上其他点距离最大值,\(s_i=(d_i+a)*b\mod c\)

    \(a,b,c\) 为给定的系数。

    每次询问给 \(x,y,z\) ,求 \(x\)\(y\) 路径上第一个 \(s_i\ge z\) 的点,不存在输出 -1

    要求 \(d_i\) ,需要知道一个性质:\(d_i\) 等于 \(i\) 到树的直径的两个端点的距离的较大值

    可以用 3 次 dfs ,实际上都是调用一个函数。

    • 从任意点出发,找到直径的一个端点 \(p1\)
    • 找到另一个端点 \(p2\) ,计算点到 \(p1\) 的距离
    • 用点到 \(p2\) 的距离更新较大值

    剩下的交给倍增,将路径拆为 \(x-lca\)\(y-lca\) 两段

    \(x-lca\) 这一段,对于两点深度差二进制拆分,看每一段是否满足条件,若满足就找到第一个

    \(y-lca\) 同理,由于是从下往上跳,可以开一个栈记录,从上往下找

    比较好理解的 \(O(n\log n)\),只是考场换根 dp 挂了

    #include <bits/stdc++.h>
    using namespace std;
    typedef unsigned long long uLL;
    typedef long double LD;
    typedef long long LL;
    typedef double db;
    const int N = 1e5 + 5, M = 3e5 + 5;
    int n, Ti, A, B, C, Ecnt, lst[N], fa[N][25], dep[N], St, lg[N], g[N][25], Q, res, st[25], top, px[25];
    LL F[N], mx;
    struct Ed { int to, nxt; LL qz; } e[M << 1];
    inline void Ae(int fr, int go, int vl) {
        e[++Ecnt] = (Ed){ go, lst[fr], 1ll * vl }, lst[fr] = Ecnt;
    }
    void dfs1(int u, int ff, LL d) {
        if (d > mx) St = u, mx = d;
        for (int i = lst[u], v; i; i = e[i].nxt)
            if ((v = e[i].to) ^ ff) F[v] = max(F[v], d + e[i].qz), dfs1(v, u, d + e[i].qz);
    }
    void dfs2(int u, int ff) {
        dep[u] = dep[fa[u][0] = ff] + 1;
        for (int i = lst[u], v; i; i = e[i].nxt) if ((v = e[i].to) ^ ff) dfs2(v, u);
    }
    inline int lca(int x, int y) {
        if (dep[x] < dep[y]) swap(x, y);
        for (int i = 17; ~i; i--) if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];
        if (x == y) return x;
        for (int i = 17; ~i; i--) if (fa[x][i] ^ fa[y][i]) x = fa[x][i], y = fa[y][i];
        return fa[x][0];
    }
    int f1(int x, int y) {
        for (int d = dep[x] - dep[y] + 1, i; d; d -= d & -d, x = fa[x][i])
            if (g[x][i = lg[d & -d]] >= Q) {
                while (i--) if (g[x][i] < Q) x = fa[x][i];
                return x;
            }
        return 0;
    }
    int f2(int x, int y) {
        top = 0;
        for (int d = dep[x] - dep[y] + 1, i; d; d -= d & -d)
            px[++top] = x, st[top] = i = lg[d & -d], x = fa[x][i];
        for (int i; top; top--) {
            if (g[x = px[top]][i = st[top]] >= Q) {
                while (i--) if (g[fa[x][i]][i] >= Q) x = fa[x][i];
                return x;
            }
        }
        return 0;
    }
    int main() {
        scanf("%d%d%d%d%d", &n, &Ti, &A, &B, &C);
        for (int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
        for (int i = 1, u, v, w; i < n; i++)
            scanf("%d%d%d", &u, &v, &w), Ae(u, v, w), Ae(v, u, w);
        dfs1(1, 0, 0), mx = 0, dfs1(St, 0, 0), mx = 0, dfs1(St, 0, 0), dfs2(1, 0);
        for (int i = 1; i <= n; i++) g[i][0] = 1ll * (F[i] + A) * B % C;
        for (int j = 1; j <= 17; j++)
            for (int i = 1; i <= n; i++)
                fa[i][j] = fa[fa[i][j - 1]][j - 1], g[i][j] = max(g[i][j - 1], g[fa[i][j - 1]][j - 1]);
        for (int x, y, l; Ti--; ) {
            scanf("%d%d%d", &x, &y, &Q);
            l = lca(x, y), res = 0;
            if ((res = f1(x, l)) || (res = f2(y, l))) printf("%d\n", res);
            else puts("-1");
        }
    }
    

    总结

    • 最小化一个值的思考
    • 状压不要搞错含义
    • 树的直径的性质
  • 相关阅读:
    需要union
    with语法,需要递归的面试题目
    聚合主分类,子查询获得子分类
    泛型
    RepeaterInMVC
    需要自己创建集合的题目
    Ollydbg入门
    svn服务器架设
    http与svn架设服务器
    svn错误信息一览表
  • 原文地址:https://www.cnblogs.com/KonjakLAF/p/16365604.html
Copyright © 2020-2023  润新知