3075 走捷径

`求在树上长为K的最短边数。
N<=2e5,K<=1e6
对70%数据k<=100

40分O(n^2)终态枚举。
枚举根,dfs访问到每个点的距离,可以最优性减枝。

70分O(n*K)树形dp。
定义dp[i][j]为到i距离为j的最短边数,考虑树上穿过i的路径时,在i子树下选2个节点x,y,要求他们不在i的同一棵子树里,这可以用先访问一遍子树求答案,再访问一遍子树记录dp值来解决。x与t的边长为v,转移的方程:MIN(dp[x][j],dp[t][j-v]+1)

100分O(nlogn)树分治。
首先,对于x而言,树上的路径只有两种:穿过x或不穿过x,因此当我们处理完穿过x的路径之后,剩下的所有情况都和x没有关系了,与x相连的所有的边都会被切断,形成若干棵子树,对于每一棵子树都是相同的操作。
再来看复杂度,每一次的操作次数是当前树的大小,因此,如果这棵树的rt是重心的话,可以保证切断与根相邻的边后,每一棵子树的大小都不超过n/2,因此操作的深度最多是logn,类似归并排序的复杂度。
接下来是处理x子树的细节。框架是一维dp,找到重心rt,dp[i]表示到rt距离为i时的最小边数。也是先访问一遍子树求答案,再访问一遍子树记录dp值。

100分O(nlogn^2)启发式合并。参考by 小c
看似暴力的外表下,有一颗logn的心。
首先,主要的思路是参考70分的树形dp。具体的过程是,用map集合来表示一个子树。
每一次和子树合并的时候,如果只是普通的合并,是用子树的所有集合塞入根的集合里,每塞入一次的操作复杂度是logn级别的,因此如果用启发式合并,每次都是把小的集合塞入到大的集合之中,那么每个数最多被塞logn次,有n个数,每次塞的复杂度是logn,整体的复杂度是O(nlogn^2),代码用指针来实现。

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<cmath>
#include<string>
#include<vector>
#include<queue>
#include<map>
using namespace std;
#define ll long long
#define db double
#define pb push_back
#define rep(i,s,t) for(i=s;i<=t;i++)
#define per(i,s,t) for(i=s;i>=t;i--)
#define lsn(p) p<<1
#define rsn(p) p<<1|1
#define lowbit(x) x&-x
#define fi first
#define se second
inline void MIN(int &a,int b){if(a==-1||a>b)a=b;}
inline void MAX(int &a,int b){if(a<b)a=b;}
template<class T> inline void rd(T &res){
    res=0;
    char c;
    int k=1;
    while(c=getchar(),c<'0')if(c=='-')k=-1;
    do{
        res=(res<<1)+(res<<3)+(c^48);
    }while(c=getchar(),c>='0');
}
template<class T> inline void pt(T k){
    if(k==0)return;
    pt(k/10);
    putchar((k%10)^48);
}
template<class T> inline void sc(T k){
    if(k<0){putchar('-');k=-k;}
    pt(k);
    if(k==0)putchar('0');
    putchar('\n');
}
const int M=2e5+3;
int n,m,ecnt,ans=-1;
int head[M];
struct edge{
    int t,v,nxt;
}e[M<<1];
map<ll,int>dp[M];//在i这个集团里,距离根的距离是ll的最小边数
map<ll,int>*p[M];//i所在集合的指针
map<ll,int>::iterator it;
inline void addedge(int f,int t,int v){
    e[++ecnt]=(edge){t,v,head[f]};
    head[f]=ecnt;
}
inline void input(){
    int i,j,k,a,b,c;
    rd(n);rd(m);
    rep(i,1,n-1){
        rd(a);rd(b);rd(c);
        addedge(a,b,c);
        addedge(b,a,c);
    }
}
inline void merge(map<ll,int> *A,map<ll,int> *B,ll d,int a){//将A合并到B上 
    //k=dis(x,y)=dis(0,x)+dis(0,y)-2*dis(0,lca)
    for(it=A->begin();it!=A->end();it++){
        ll len=m+1ll*2*d-(it->fi);
        if(B->find(len)!=B->end())MIN(ans,it->se+(*B)[len]-2*a);
    }
    for(it=A->begin();it!=A->end();it++){
        if(B->find(it->fi)==B->end())(*B)[it->fi]=it->se;
        else MIN((*B)[it->fi],it->se);
    }
}
inline void dfs(int x,int f,ll d,int a){
    dp[x][d]=a;
    p[x]=&dp[x];
    int i,t,v;
    for(i=head[x];i;i=e[i].nxt){
        t=e[i].t,v=e[i].v;
        if(t==f)continue;
        dfs(t,x,d+e[i].v,a+1);
        //与儿子的集合合并 
        if(p[x]->size()<p[t]->size()){
            merge(p[x],p[t],d,a);
            p[x]->clear();
            p[x]=p[t];
        }else{
            merge(p[t],p[x],d,a);
            p[t]->clear();
        }
    }
}
int main(){
    input();
    dfs(0,-1,0,0);
    sc(ans);
    return 0;
} 
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<cmath>
#include<string>
#include<vector>
#include<queue>
#include<map>
using namespace std;
#define ll long long
#define db double
#define pb push_back
#define rep(i,s,t) for(i=s;i<=t;i++)
#define per(i,s,t) for(i=s;i>=t;i--)
#define lsn(p) p<<1
#define rsn(p) p<<1|1
#define lowbit(x) x&-x
inline void MIN(int &a,int b){if(a==-1||a>b)a=b;}
inline void MAX(int &a,int b){if(a<b)a=b;}
template<class T> inline void rd(T &res){
    res=0;
    char c;
    int k=1;
    while(c=getchar(),c<'0')if(c=='-')k=-1;
    do{
        res=(res<<1)+(res<<3)+(c^48);
    }while(c=getchar(),c>='0');
}
template<class T> inline void pt(T k){
    if(k==0)return;
    pt(k/10);
    putchar((k%10)^48);
}
template<class T> inline void sc(T k){
    if(k<0){putchar('-');k=-k;}
    pt(k);
    if(k==0)putchar('0');
    putchar('\n');
}
const int M=2e5+3;
int head[M],dp[1000005];
int n,m,ecnt,ans=-1;
struct edge{
    int t,v,nxt;
}e[M<<1];
inline void addedge(int f,int t,int v){
    e[++ecnt]=(edge){t,v,head[f]};
    head[f]=ecnt;
}
inline void input(){
    memset(dp,-1,sizeof(dp));
    dp[0]=0;
    int i,j,k,a,b,c;
    rd(n);rd(m);
    rep(i,1,n-1){
        rd(a);rd(b);rd(c);
        addedge(a,b,c);
        addedge(b,a,c);
    }
}
struct P40{
    inline void dfs(int x,int f,int d,int a){
        if(d>m)return;
        if(~ans&&a>=ans)return;
        if(d==m)MIN(ans,a);
        for(int i=head[x];i;i=e[i].nxt){
            int t=e[i].t,v=e[i].v;
            if(t!=f)dfs(t,x,d+v,a+1);
        }
    }
    inline void solve(){
        int i,j,k;
        rep(i,0,n-1)dfs(i,-1,0,0);
        sc(ans);
    }
}P40;
struct P70{
    int dp[101][M];//dp[j][i] i为根,距离j的最少步数 
    inline void dfs(int x,int f){
        dp[0][x]=0;
        int i,j,t,v;
        for(i=head[x];i;i=e[i].nxt){
            t=e[i].t,v=e[i].v;
            if(t==f)continue;
            dfs(t,x);
            rep(j,0,m-v)
                if(~dp[j][x]&&~dp[m-j-v][t])MIN(ans,dp[j][x]+dp[m-j-v][t]+1);
            rep(j,0,m-v)
                if(~dp[j][t])MIN(dp[j+v][x],dp[j][t]+1);
        }
    }
    inline void solve(){
        int i,j,k;
        memset(dp,-1,sizeof(dp));
        dfs(0,-1);
        sc(ans);
    }
}P70;
struct P100{
    int que[M],sz[M],mx[M],tot,cnt;
    bool mark[M];
    inline void dfs_sz(int x,int f){
        que[tot++]=x;
        sz[x]=mx[x]=1;
        for(int i=head[x];i;i=e[i].nxt){
            int t=e[i].t,v=e[i].v;
            if(t==f||mark[t])continue;
            dfs_sz(t,x);
            MAX(mx[x],sz[t]+1);
            sz[x]+=sz[t];
        }
    }
    inline int find(){//重心 
        int i,j,k=0;//k在队列中的位置 
        rep(i,0,tot-1){
            MAX(mx[que[i]],tot-sz[que[i]]+1);
            if(mx[que[i]]<mx[que[k]])k=i;
        }
        return que[k];
    }
    inline void dfs(int x,int f,int d,int a){
        if(d>m)return;
        if(~dp[m-d])MIN(ans,a+dp[m-d]);
        for(int i=head[x];i;i=e[i].nxt){
            int t=e[i].t,v=e[i].v;
            if(t==f||mark[t])continue;
            dfs(t,x,d+v,a+1);
        }
    }
    inline void rdfs(int x,int f,int d,int a){
        if(d>m)return;
        MIN(dp[d],a);
        for(int i=head[x];i;i=e[i].nxt){
            int t=e[i].t,v=e[i].v;
            if(t==f||mark[t])continue;
            rdfs(t,x,d+v,a+1);
        }
    }
    inline void clear(int x,int f,int d){
        if(d>m)return;
        dp[d]=-1;
        for(int i=head[x];i;i=e[i].nxt){
            int t=e[i].t,v=e[i].v;
            if(t==f||mark[t])continue;
            clear(t,x,d+v);
        }
    }
    inline void solve(int x){//处理x所在的联通块 
        int i,t,v;
        tot=0;
        dfs_sz(x,-1);
        x=find();//重心
//      printf("%d\n",x);
        mark[x]=1;
        for(i=head[x];i;i=e[i].nxt){
            t=e[i].t;
            if(mark[t])continue;
            dfs(t,x,e[i].v,1);  
            rdfs(t,x,e[i].v,1);
        }
        for(i=head[x];i;i=e[i].nxt){
            if(mark[e[i].t])continue;
            clear(e[i].t,x,e[i].v);
        }
        for(i=head[x];i;i=e[i].nxt){
            t=e[i].t;
            if(mark[t])continue;
            solve(t);
        }
    }
}P100;
int main(){
//  freopen("0.in","r",stdin);
    input();
    if(n<=1000)P40.solve();
    else if(m<=100)P70.solve();
    else P100.solve(0),sc(ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值