比赛题目训练系列16 (2021浙江省赛)

比赛题目训练系列16 (2021浙江省赛)

训练地址

D. Shortest Path Query

  • 给至多 1 0 5 10^5 105 个点,至多 2 ∗ 1 0 5 2 * 10^5 2105 条双向边(边有权重). 边有一个限制,就是两个顶点的编号,一个一定是另一个的二进制下的前缀。现在给至多 1 0 5 10^5 105 个询问,给定两个结点,问两结点之间最短距离是多少.
  • 任意两点之间 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 second
using namespace std;
const int N = 100010, M = 400010;
int h[N], e[M], ne[M], w[M], idx;
typedef long long ll;
const ll INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<ll, ll> P;
ll d[N], dist[N][20];
int vis[N], vis2[N];
void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int cal(int u, int v)
{
    int cnt = 0;
    while(v > u){
        cnt++;
        v >>= 1;
    }
    return cnt;
}
void dijkstra(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});
            }
        }
    }
}
int lca(int u, int v)
{
    while(u != v){
        if(u > v) u >>= 1;
        else v >>= 1;
    }
    return u;
}
int Length(int u)
{
    int cnt = 0;
    while(u){
        cnt++;
        u >>= 1;
    }
    return cnt;
}
int main()
{
    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");
        else printf("%lld\n", ans);
    }
    return 0;
}

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 n108,m108,且有不超过1000次询问.
  • n ≥ m n \ge m nm 的情况非常显然答案是 n − m n - m nm. 我们只考虑 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,n1],那么 m m m 应该跑到离 n − x n-x nx 的倍数最近的地方,即 ( ⌈ m n − x ⌉ ∗ ( n − x ) ) (\lceil \frac{m}{n-x} \rceil * (n-x)) (nxm(nx)). 令 i = n − x i = n - x i=nx,则 i ∈ [ 1 , n ] i \in [1, n] i[1,n]. 则 m m m 对答案的贡献是 ⌈ m i ⌉ ∗ i − i \lceil \frac{m}{i} \rceil * i - i imii. n n n 对答案的贡献是 n − i n - i ni.
  • 向上取整转向下取整: ⌈ m n ⌉ = ⌊ m − 1 n ⌋ + 1 \lceil \frac{m}{n} \rceil = \lfloor \frac{m - 1}{n} \rfloor + 1 nm=nm1+1. 因此 m m m 对答案的贡献是 ⌊ m − 1 i ⌋ ∗ i + i − m \lfloor \frac{m-1}{i} \rfloor * i + i - m im1i+im
  • 则最终的答案为 n − m + ⌊ m − 1 i ⌋ ∗ i , i ∈ [ 1 , n ] n - m + \lfloor \frac{m-1}{i} \rfloor * i, i \in [1, n] nm+im1i,i[1,n]. 这个可以用整除分块来写.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll g(ll m, ll x)
{
    return m / (m / x);
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--){
        ll n, m;
        scanf("%lld%lld", &n, &m);
        if(m % n == 0){
            printf("0\n");
        }
        else if(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);
        }

    }
    return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值