任意两点之间
s
,
t
s, t
s,t 的距离的最小值,等于从
s
s
s 到两者所有公共祖先,再到
t
t
t 的最小值.
我们先预处理出所有以此节点为
r
o
o
t
root
root 的子树构成的子图,跑一遍最短路。这样就处理出了从此节点为根节点的子图的单源最短路.
有一个问题是,跑
n
n
n 遍dijkstra,不能每次都初始化vis和d数组。那么用两个时间戳,第一个记录当前点
v
i
s
[
u
]
vis[u]
vis[u] 是否等于 root,如果相等的话,从 root 到 u 的最短路就找到了。第二个是记录当前点的从 u 到 v.
v
i
s
2
[
v
]
vis2[v]
vis2[v] 是否 root. 如果是的话,那就把
d
[
v
]
d[v]
d[v] 初始化为 INF.
#include<bits/stdc++.h>#define x first#define y secondusingnamespace std;constint N =100010, M =400010;int h[N], e[M], ne[M], w[M], idx;typedeflonglong ll;const ll INF =0x3f3f3f3f3f3f3f3f;typedef pair<ll, ll> P;
ll d[N], dist[N][20];int vis[N], vis2[N];voidadd(int a,int b,int c){
e[idx]= b, ne[idx]= h[a], w[idx]= c, h[a]= idx++;}intcal(int u,int v){int cnt =0;while(v > u){
cnt++;
v >>=1;}return cnt;}voiddijkstra(int root){
priority_queue<P, vector<P>, greater<P>> que;
que.push({0, root});//这里不要忘记初始化.
d[root]=0;
vis2[root]= root;while(que.size()){auto p = que.top(); que.pop();int u = p.y;if(vis[u]== root)continue;
vis[u]= root;
dist[u][cal(root, u)]= d[u];for(int i = h[u]; i !=-1; i = ne[i]){int v = e[i];if(v < root)continue;if(vis2[v]!= root){
d[v]= INF;
vis2[v]= root;}if(d[v]> d[u]+ w[i]){
d[v]= d[u]+ w[i];
que.push({d[v], v});}}}}intlca(int u,int v){while(u != v){if(u > v) u >>=1;else v >>=1;}return u;}intLength(int u){int cnt =0;while(u){
cnt++;
u >>=1;}return cnt;}intmain(){int n, m, q;scanf("%d%d",&n,&m);memset(dist,0x3f,sizeof dist);memset(h,-1,sizeof h);for(int i =1; i <= n; i++){
dist[i][0]=0;}for(int i =0; i < m; i++){int a, b, c;scanf("%d%d%d",&a,&b,&c);add(b, a, c),add(a, b, c);}scanf("%d",&q);for(int i =1; i <= n; i++){dijkstra(i);}while(q--){int s, t;scanf("%d%d",&s,&t);
ll ans = INF;int LA =lca(s, t);//printf("*** %d %d %d\n", s, t, LA);int l1 =Length(s)-Length(LA), l2 =Length(t)-Length(LA);while(LA){
ans =min(ans, dist[s][l1]+ dist[t][l2]);
LA >>=1;
l1++, l2++;}if(ans == INF)printf("-1\n");elseprintf("%lld\n", ans);}return0;}
F. Fair Distribution
给两个数字
n
,
m
n, m
n,m,一次操作可以对
n
n
n 减 1 或者对
m
m
m 加1. 问至少需要多少次操作可以让 n 整除 m. 其中
n
≤
1
0
8
,
m
≤
1
0
8
n \le 10^8, m \le 10^8
n≤108,m≤108,且有不超过1000次询问.
n
≥
m
n \ge m
n≥m 的情况非常显然答案是
n
−
m
n - m
n−m. 我们只考虑
n
<
m
n < m
n<m 的情况.
我们发现,如果确定了新的
n
n
n,那么对应的 m 的最小变化会唯一确定。
假设
n
n
n 减小了
x
,
x
∈
[
0
,
n
−
1
]
x, x \in [0, n - 1]
x,x∈[0,n−1],那么
m
m
m 应该跑到离
n
−
x
n-x
n−x 的倍数最近的地方,即
(
⌈
m
n
−
x
⌉
∗
(
n
−
x
)
)
(\lceil \frac{m}{n-x} \rceil * (n-x))
(⌈n−xm⌉∗(n−x)). 令
i
=
n
−
x
i = n - x
i=n−x,则
i
∈
[
1
,
n
]
i \in [1, n]
i∈[1,n]. 则
m
m
m 对答案的贡献是
⌈
m
i
⌉
∗
i
−
i
\lceil \frac{m}{i} \rceil * i - i
⌈im⌉∗i−i.
n
n
n 对答案的贡献是
n
−
i
n - i
n−i.
向上取整转向下取整:
⌈
m
n
⌉
=
⌊
m
−
1
n
⌋
+
1
\lceil \frac{m}{n} \rceil = \lfloor \frac{m - 1}{n} \rfloor + 1
⌈nm⌉=⌊nm−1⌋+1. 因此
m
m
m 对答案的贡献是
⌊
m
−
1
i
⌋
∗
i
+
i
−
m
\lfloor \frac{m-1}{i} \rfloor * i + i - m
⌊im−1⌋∗i+i−m
则最终的答案为
n
−
m
+
⌊
m
−
1
i
⌋
∗
i
,
i
∈
[
1
,
n
]
n - m + \lfloor \frac{m-1}{i} \rfloor * i, i \in [1, n]
n−m+⌊im−1⌋∗i,i∈[1,n]. 这个可以用整除分块来写.
#include<bits/stdc++.h>usingnamespace std;typedeflonglong ll;
ll g(ll m, ll x){return m /(m / x);}intmain(){int T;scanf("%d",&T);while(T--){
ll n, m;scanf("%lld%lld",&n,&m);if(m % n ==0){printf("0\n");}elseif(n > m){printf("%lld\n", n - m);}else{
ll res =1e9;for(ll l =1, r; l <= n; l = r +1){
r =min(n,g(m -1, l));if(l <= n) res =min(res,(m -1)/ l * l);//printf("%lld %lld\n", l, res);}printf("%lld\n", n - m + res);}}return0;}