其他分享
首页 > 其他分享> > 倍增的两道应用题(题解报告)

倍增的两道应用题(题解报告)

作者:互联网

文章目录

洛谷 P1967 [NOIP2013 提高组] 货车运输

题目链接
题意:题意不难看懂,就是要求两点间道路中最大权值的最小值
思路:这道题有很多种解法,这里讲利用LCA的解法
首先我们知道,解肯定是建立在最大生成树上的
先用kruscal建最大生成树
然后再在LCA中维护一个d(u,v)表示从节点u走到节点v经过的道路的最小权重
然后就可以上代码了
AC代码:

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int N = 1e5 + 10,M = 5e5 + 10,INF = 0x3f3f3f3f;
int head[N],tot;
int n,m,q;
int Fa[N];
int fa[N][20],d[N][20],dep[N];
int vis[N];
struct Edge{
    int v,w,next;
}e[M << 1];
struct node{
    int u,v,w;
    bool operator < (const node& b)const{
        return w > b.w;
    }
}a[N];
void add(int u,int v,int w){
    e[tot].v = v;
    e[tot].next = head[u];
    e[tot].w = w;
    head[u] = tot ++;
}
int getf(int x){return x == Fa[x] ? x : Fa[x] = getf(Fa[x]);}
void kruscal(){
    for(int i = 1;i <= n;i ++) Fa[i] = i;
    sort(a,a + m);
//    for(int i = 0;i < m;i ++) cout << a[i].w << " ";
//    cout << endl;
    for(int i = 0;i < m;i ++){
        int fu = getf(a[i].u);
        int fv = getf(a[i].v);
        int u = a[i].u,v = a[i].v,w = a[i].w;
        if(fu != fv){
            Fa[fu] = fv;
            add(u,v,w);
            add(v,u,w);
        }
    }
}

void dfs(int u){
    vis[u] = 1;
    for(int i = 1;i <= 16;i ++){
        if(dep[u] < (1 << i)) break;
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
        d[u][i] = min(d[u][i - 1],d[fa[u][i - 1]][i - 1]);
    }
    for(int i = head[u];~i;i = e[i].next){
        int v = e[i].v,w = e[i].w;
        if(v != fa[u][0]){
            fa[v][0] = u;
            dep[v] = dep[u] + 1;
            d[v][0] = w;
            dfs(v);
        }
    }
}
int LCA(int x,int y){
    if(dep[x] < dep[y]) swap(x,y);
    int t = dep[x] - dep[y];
    for(int i = 0;i <= 16;i ++){
        if(t & (1 << i)) x = fa[x][i];
    }
    if(x == y) return x;
    for(int i = 16;i >= 0;i --){
        if(fa[x][i] != fa[y][i]){
            x = fa[x][i];
            y = fa[y][i];
        }
    }
    return fa[x][0];
}

int solve(int u,int v){
    int t = dep[u] - dep[v];
    int ans = INF;
    for(int i = 0;i <= 16;i ++){
        if(t & (1 << i)){
            ans = min(ans,d[u][i]);
            u = fa[u][i];
        }
    }
    return ans;
}
int main(){
    memset(head,-1,sizeof(head));
    cin >> n >> m;
    for(int i = 0;i < m;i ++){
        cin >> a[i].u >> a[i].v >> a[i].w;
    }
    kruscal();//最大生成树
    for(int i = 1;i <= n;i ++)//预处理出fa[][],d[][],dep[]
        if(!vis[i]) dfs(i);

    cin >> q;
    for(int i = 0;i < q;i ++){
        int u,v;
        cin >> u >> v;
        if(getf(u) != getf(v)) cout << -1 << endl;
        else{
            int t = LCA(u,v);
            cout << min(solve(u,t),solve(v,t)) << endl;
        }
    }
    return 0;
}

牛客 Genius ACM

题目链接
ps:这道题的优化真的很妙!!
题意:首先易证校验值就是一个有序数组里,最大与最小的差的平方加上次大与次小的差的平方加上…依次类推
然后这个问题就是一个贪心问题了
思路:可以用倍增优化一下取值操作,再用归并优化一下排序操作 因为要算校验值数组必须有序,那么已经取到的数必然是有序的,新加进去的若干数只需要先自身排序再与前面的数归并一下即可
AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int N = 5e5 + 10;
int T,n,m;
typedef long long ll;
ll k;
ll a[N],b[N],c[N];
int ans,R;

void Merge(int l,int mid,int r){
    int i = l,j = mid + 1,t = l;
    while(i <= mid && j <= r){
        if(b[i] <= b[j]) c[t ++] = b[i ++];
        else c[t ++] = b[j ++];
    }
    while(i <= mid) c[t ++] = b[i ++];
    while(j <= r) c[t ++] = b[j ++];
}

ll cal(int l,int r){
    ll res = 0;
    for(int i = R + 1;i <= r;i ++){
        b[i] = a[i];
    }
    sort(b + R + 1,b + 1 + r);
    Merge(l,R,r);
    for(int i = 1;i <= min(m,(r - l + 1) >> 1);i ++){
        res += (c[l + i - 1] - c[r - i + 1]) * (c[l + i - 1] - c[r - i + 1]);
    }
    return res;
}
int work(int l){
    int p = 1,r = l - 1;
    while(p){
        ll res = cal(l,min(n,r + p));
        if(res <= k){
            R = r = min(n,r + p);
            for(int i = l;i <= r;i ++) b[i] = c[i];
            if(r == n) break;
            p <<= 1;
        }
        else p >>= 1;
    }
    return r;
}
int main(){
    scanf("%d",&T);
    while(T --){
        ans = 0;
        R = 0;
        scanf("%d%d%lld",&n,&m,&k);
        for(int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
        int l = 1;
        while(l <= n){
            l = work(l) + 1;
            ans ++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

标签:include,return,fa,int,题解,应用题,dep,ans,倍增
来源: https://blog.csdn.net/cpp_juruo/article/details/113937898