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");
    }
}

总结

  • 最小化一个值的思考
  • 状压不要搞错含义
  • 树的直径的性质
posted @ 2022-06-11 12:07  小蒟蒻laf  阅读(27)  评论(0编辑  收藏  举报