【解题总结】牛客练习赛 67

总体难度递增。感觉 E 题的做法比较新奇。F 题在 51nod 有强化版本。

E 牛妹游历城市

题意:给定 n n 个点,第 i i 个点有权值 a i a_i 。如果对于 i , j i, j a i and a j a_i \operatorname{and} a_j 不为 0 0 ,那么 i , j i, j 间有无向边,边权为 lowbit ( a i and a j ) \operatorname{lowbit}(a_i \operatorname{and} a_j) 。问从 1 1 n n 的最短路。

我们可以依次考虑每一个位,然后把点权有相同位的点互相连一条边。但这样边的数目可能是 O ( n 2 ) O(n^2)

更好的做法是对当前位建立一个虚拟点,设为 u u 。遍历所有点,如果 i i 的点权满足这一位上为 1 1 ,那么连一条从 i i u u 的边,边权为这一位对应的二进制数;再连一条从 u u i i 的边,边权为 0 0 。这样边数就下降到了至多 64 n 64n 。跑 Dijkstra 可以通过。

本题题解区还有些更高妙的做法。

#include <bits/stdc++.h>
#define REP(temp, init_val, end_val) for (int temp = init_val; temp <= end_val; ++temp)
#define REPR(temp, init_val, end_val) for (int temp = init_val; temp >= end_val; --temp)
using namespace std;
typedef long long ll;
int read(){
    int f = 1, x = 0;
    char c = getchar();
    while (c < '0' || c > '9'){if(c == '-') f = -f; c = getchar();}
    while (c >= '0' && c <= '9')x = x * 10 + c - '0', c = getchar();
    return f * x; 
}
int n;
unsigned a[100005];
ll dis[100005 + 50];
priority_queue<pair<int, ll>, vector<pair<int, ll> >, 
    greater<pair<int, ll> > > pq;
int to[6400005], nxt[6400006], at[100005 + 50], cnt;
unsigned w[6400005];
void init(){
    n = read();
    REP(i, 1, n) scanf("%u", &a[i]);
    // build graph
    memset(at, 0, sizeof(at));
    cnt = 0;
    int nn = n;
    for (unsigned t = 1; t > 0; t <<= 1){
        ++n;
        REP(i, 1, nn){
            if (a[i] & t){
                to[++cnt] = n, nxt[cnt] = at[i], w[cnt] = t, at[i] = cnt;
                to[++cnt] = i, nxt[cnt] = at[n], w[cnt] = 0, at[n] = cnt;
            }
        }
    }
}
void solve(){
    memset(dis, 0x3f, sizeof(dis));
    ll lim = dis[1];
    dis[1] = 0;
    pq.push(make_pair(1, 0));
    for (; ; ){
        while (!pq.empty()){
            if (pq.top().second > dis[pq.top().first])
                pq.pop();
            else break;
        }
        if (pq.empty()) break;
        int h = pq.top().first;
        ll dd = pq.top().second;
        pq.pop();
        for (int i = at[h]; i; i = nxt[i]){
            if (dd + w[i] < dis[to[i]])
                dis[to[i]] = dd + w[i], 
                pq.push(make_pair(to[i], dis[to[i]]));
        }
    }

    if (dis[n - 32] == lim){
        printf("Impossible\n");
    }else printf("%lld\n", dis[n - 32]);
}
int main(){
    int T = read();
    while (T--){
        init();
        solve();
    }
    return 0;
}

F 牛妹的苹果树

题意:给定一棵树, q q 次询问,每次给出 l , r l, r ,求 max l u , v r dist ( u , v ) \max_{l \le u ,v \le r}\operatorname{dist}(u, v)

由直径的性质:设对于树上点集 S S ,其内部两两点之间具有最长距离的点集为 F ( S ) F(S) 。对于任意两个点集 S 1 , S 2 S_1, S_2 ,有 F ( S 1 S 2 ) F ( S 1 ) F ( S 2 ) F(S_1 \cup S_2) \subset F(S_1) \cup F(S_2)

这表明直径具有区间可合并性。从而我们可以用线段树或者 ST 表来维护一段区间内直径的两个端点和长度。要合并两个区间的答案时,新直径要么直接来自老直径,要么由两个直径各取一端点组成。

时间复杂度为 O ( ( n + q ) log n ) O((n+q)\log n)

51nod 上有强化版本:51nod 1766。当然做法是一样的。

#include <bits/stdc++.h>
#define INF 2000000000
#define M 1000000007
using namespace std;
typedef long long ll;
int read(){
    int f = 1, x = 0;
    char c = getchar();
    while(c < '0' || c > '9'){if(c == '-') f = -f; c = getchar();}
    while(c >= '0' && c <= '9')x = x * 10 + c - '0', c = getchar();
    return f * x; 
}
int n, q;
int to[600005], w[600005], at[300005] = {0}, nxt[600005], cnt = 0;
int st[300005][20][2];
ll stlen[300005][20];
int fa[300005][20], dep[300005];
ll dist[300005];
void dfs(int cur, int f){
    fa[cur][0] = f;
    for (int j = 1; j < 20; ++j){
        if (!fa[cur][j - 1]) break;
        fa[cur][j] = fa[fa[cur][j - 1]][j - 1];
    }
    for (int i = at[cur]; i; i = nxt[i]){
        int v = to[i];
        if (v == f) continue;
        dep[v] = dep[cur] + 1;
        dist[v] = dist[cur] + w[i];
        dfs(v, cur);
    }
}
int get_lca(int x, int y){
    if (dep[x] != dep[y]){
        if (dep[x] < dep[y]) swap(x, y);
        int diff = dep[x] - dep[y];
        for (int t = 1, p = 0; diff > 0; t <<= 1, ++p)
            if (t & diff)
                x = fa[x][p], diff -= t; 
    }
    if (x == y) return x;
    for (int j = 19; j >= 0; --j)
        if (fa[x][j] != fa[y][j])
            x = fa[x][j], y = fa[y][j];
    return fa[x][0]; 
}
ll get_dis(int x, int y){
    int lca = get_lca(x, y);
    return dist[x] + dist[y] - 2ll * dist[lca];
}
void update(int* uu, int* vv, ll stlen1, ll stlen2, int& u_new, int& v_new, ll& stllen){
    u_new = v_new = 0, stllen = -1;
    for (int u = 0; u < 2; ++u)
        for (int v = 0; v < 2; ++v){
            ll tmpdis = get_dis(uu[u], vv[v]);
            if (tmpdis > stllen){
                u_new = uu[u], v_new = vv[v], stllen = tmpdis;
            }
        }
    if (stlen1 > stllen)
        u_new = uu[0], v_new = uu[1], stllen = stlen1;
    if (stlen2 > stllen)
        u_new = vv[0], v_new = vv[1], stllen = stlen2;
}
void init(){
    n = read(), q = read();
    for (int i = 1; i < n; ++i){
        int u = read(), v = read(), ww = read();
        to[++cnt] = v, nxt[cnt] = at[u], w[cnt] = ww, at[u] = cnt;
        to[++cnt] = u, nxt[cnt] = at[v], w[cnt] = ww, at[v] = cnt;
    }
    dep[1] = 0, dist[1] = 0, dfs(1, 0);
    
    int logg = 0;
    while ((1 << logg) < n) ++logg;
    for (int i = 1; i <= n; ++i){
        st[i][0][0] = st[i][0][1] = i, stlen[i][0] = 0;
    }
    for (int i = 1; i < logg; ++i){
        int len = (1 << i >> 1);
        for (int j = 1; j + len - 1 <= n; ++j){
            update(st[j][i - 1], st[j + len][i - 1], stlen[j][i - 1], stlen[j + len][i - 1], 
                st[j][i][0], st[j][i][1], stlen[j][i]);
        }
    }
}
void solve(){
    while (q--){
        int l = read(), r = read();
        if (l == r){
            printf("0\n");
            continue;
        }
        int logg = 0;
        while ((2 << logg) < r - l + 1) ++logg;
        int tmpu, tmpv;
        ll res;
        update(st[l][logg], st[r - (1 << logg) + 1][logg], 
            stlen[l][logg], stlen[r - (1 << logg) + 1][logg], 
            tmpu, tmpv, res);
        printf("%lld\n", res);
    }
}
int main(){
    init();
    solve();
    return 0;
}

小结

直径的性质之前用过,但是没有细究,遭报应了。

扫描二维码关注公众号,回复: 11644988 查看本文章

这场比赛坑点比较多,也交了 3 次 WA,只能说多多注意细节吧。

猜你喜欢

转载自blog.csdn.net/zqy1018/article/details/108030550
67