树上倍增

11 篇文章 1 订阅

寒假集训第一天和最后一天分别考了这两道题,这两道题做法几乎完全一样,可谓首尾呼应,然而我还是没做对(捂脸),果然还是编程能力有问题。


题目一


题目二


两道题做法均为先Kruskal求最大或最小生成树,之后被增求LCA,同时记录最值。
个人认为难点有二:

  • 初始化时要同时记录一个跟anc结构相似的数组记录最值,以便倍增往上跳时快速求出最值
  • 处理森林,需要对每棵BSTdfs初始化

    注:当时看到题目上写不保证没有重边和自环还担心要不要特判一下,其实Kruskal完全可以处理带有重边和自环的图


下面是Nav的代码,Truck的话就是把impossible改成-1、cmp改个符号、求最小值即可

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
using namespace std;

const int MAXN=1e5,MAXM=3e5;
void swap(int & a,int & b){int t=a;a=b;b=t;}

struct node{int u,v,w;} a[MAXM+1];int acnt;
bool cmp(const node & a,const node & b){return a.w<b.w;}

int head[MAXN+1],ecnt;
struct edge{int next,to,value;} e[MAXM+1];
void add(int x,int y,int z){ecnt++,e[ecnt].to=y,e[ecnt].next=head[x],head[x]=ecnt,e[ecnt].value=z;}

int fa[MAXN+1];
int getfa(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=getfa(fa[x]);
}
void unionset(int x,int y){fa[getfa(x)]=getfa(y);}

int N,M,Q;
int anc[MAXN+1][32];
int deep[MAXN+1];
int maxv[MAXN+1][32];
void dfs(int now,int from)
{
    for(int tmp=head[now];tmp;tmp=e[tmp].next)
    {
        if(e[tmp].to==from) continue;
        deep[e[tmp].to]=deep[now]+1;
        anc[e[tmp].to][0]=now;
        maxv[e[tmp].to][0]=e[tmp].value;
        dfs(e[tmp].to,now);
    }
}
void ready()
{
    int i,j;
    for(i=1;(1<<i)<=N;i++)
        for(j=1;j<=N;j++)
        {
            anc[j][i]=anc[anc[j][i-1]][i-1];
            maxv[j][i]=max(maxv[j][i-1],maxv[anc[j][i-1]][i-1]);
        }
}
int ans;
int getlca(int x,int y)
{
    if(deep[x]<deep[y]) swap(x,y);
    int maxlogn=floor(log(N)/log(2));
    int i,j;
    for(i=maxlogn;i>=0;i--)
        if(deep[x]-(1<<i)>=deep[y])
            ans=max(ans,maxv[x][i]),x=anc[x][i];
    if(x==y) return x;
    for(i=maxlogn;i>=0;i--)
        if(anc[x][i]!=anc[y][i])
        {
            ans=max(ans,max(maxv[x][i],maxv[y][i]));
            x=anc[x][i];y=anc[y][i];
        }
    ans=max(ans,maxv[x][0]);ans=max(ans,maxv[y][0]);
    return anc[x][0];
}
int main()
{
    freopen("nav.in","r",stdin);
    freopen("nav.out","w",stdout);
    int i;
    cin>>N>>M;
    for(i=1;i<=M;i++)
    {
        acnt++;
        scanf("%d%d%d",&a[acnt].u,&a[acnt].v,&a[acnt].w);
    }
    sort(a+1,a+M+1,cmp);
    int tot=0;
    for(i=1;i<=N;i++) fa[i]=i;
    for(i=1;i<=M;i++)
    {
        if(getfa(a[i].u)!=getfa(a[i].v))
        {
            unionset(a[i].u,a[i].v);
            tot++;
            add(a[i].u,a[i].v,a[i].w);
            add(a[i].v,a[i].u,a[i].w);
        }if(tot>=N-1) break;
    }
    for(i=1;i<=N;i++)//对森林中每棵MST进行dfs预处理
        if(!anc[i][0])
            dfs(getfa(i),getfa(i));
    ready();
    cin>>Q;
    for(i=1;i<=Q;i++)
    {
        int A,B;
        scanf("%d%d",&A,&B);
        if(getfa(A)!=getfa(B))//判断A、B两点是否在同一MST中
        {
            printf("impossible\n");
            continue;
        }
        ans=0;
        getlca(A,B);
        printf("%d\n",ans);
    }
    return 0;
}

所以说我是跟树上倍增有仇吗,今天又考了一道,树上倍增优化动规,然而我把状态转移方程写错了……

状态转移方程:

f[u]=min{f[father]+wi(deep[father]deep[u]<=ki)

边界条件:

f[1]=0

代码

#include<iostream>
#include<cstdio>
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
using namespace std;

typedef long long ll;
const int MAXN=1e5,MAXM=1e5,MAXQ=1e5,INF=~0U>>1;
int n,m,q;

struct E{int to,next;} e[MAXM+1];int ecnt,G[MAXN+1];
void addEdge(int u,int v){e[++ecnt]=(E){v,G[u]};G[u]=ecnt;}

struct T{int next,k,w;} tic[MAXN+1];int tcnt,tH[MAXN+1];
void addTic(int v,int k,int w){tic[++tcnt]=(T){tH[v],k,w};tH[v]=tcnt;}

int anc[MAXN+1][18],tmin[MAXN+1][18];
int f[MAXN+1];
void dfs(int u,int fa)
{
    int i;
    anc[u][0]=fa,tmin[u][0]=f[fa];
    for(i=1;i<=18;i++)
    {
        anc[u][i]=anc[anc[u][i-1]][i-1];
        tmin[u][i]=min(tmin[u][i-1],tmin[anc[u][i-1]][i-1]);
    }
    f[u]=u==1?0:INF;
    for(i=tH[u];i;i=tic[i].next)
    {
        ll tmp=INF,v=u,k=tic[i].k,p=0;
        while(k)
        {
            if(k&1)
            {
                tmp=min(tmp,tmin[v][p]);
                v=anc[v][p];
            }p++,k>>=1;
        }
        f[u]=min(f[u],tmp+tic[i].w);
    }

    for(i=G[u];i;i=e[i].next)
        dfs(e[i].to,u);
}

int main()
{
    freopen("party.in","r",stdin);
    freopen("party.out","w",stdout);
    int i;
    scanf("%d%d",&n,&m);
    for(i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        addEdge(v,u);
    }
    for(i=1;i<=m;i++)
    {
        int v,k,w;
        scanf("%d%d%d",&v,&k,&w);
        addTic(v,k,w);
    }
    dfs(1,1);
    scanf("%d",&q);
    while(q--)
    {
        int pos;
        scanf("%d",&pos);
        printf("%d\n",f[pos]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值