高峰期
\(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\) ,就能得到最小值
解得最小的 \(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");
}
}
总结
- 最小化一个值的思考
- 状压不要搞错含义
- 树的直径的性质