IOI2011 race

2 篇文章 0 订阅
2 篇文章 0 订阅

Task:
给定一棵带权树,求出边数最小的一条路径使得路径长度为K.
1 ≤ N ≤ 200000 ,1 ≤ K ≤ 1000000

Solution:
枚举路径的lca为节点x,只考虑一定经过x的路径.
再枚举其中的一个端点y,设y到x的距离为d1,确定了y,我们就知道了路径另一个端点到x的距离了,现在问题就是求出到x点距离为K-d1的路径的最少边数,那么只要在遍历x子树过程中实时记录该信息即可.
那么每个节点会被它所有的父亲遍历到,如果给出的树是随机构造的,复杂度就是nlogn.对于极端数据,利用点分治的方法来优化,使每个节点被计算的次数最多为logn.

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<queue>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<map>
#include<set>
#define ll long long 
#define y1 abcdedhf
#define pb push_back
using namespace std;
inline void rd(int &res){
    res=0;char c;
    while(c=getchar(),c<48);
    do res=(res<<1)+(res<<3)+(c^48);
    while(c=getchar(),c>=48);
}
void pt(int x){
    if(!x)return;
    pt(x/10);
    putchar((x%10)+'0');
}
void sc(int x){
    if(x<0){x=-x;putchar('-');}
    if(!x)putchar('0');
    pt(x);
    putchar('\n');
}
const int M=2e5+5;
const int S=1e6+5;
struct node{
    int to,v,nex;
}e[M<<1];
int mx[M],sz[M],dp[S],n,head[M],ec=0,rt,m,ans=-1;//两千万 
bool vis[M];
inline void Min(int &x,int y){if(x==-1||x>y)x=y;}
void ins(int a,int b,int v){
    e[ec]=(node){b,v,head[a]};
    head[a]=ec++;
    e[ec]=(node){a,v,head[b]};
    head[b]=ec++;
}
void dfs(int x,int f){
    sz[x]=1;
    mx[x]=0;
    for(int i=head[x];~i;i=e[i].nex){
        int y=e[i].to;
        if(vis[y]||y==f)continue;
        dfs(y,x);
        sz[x]+=sz[y];
        mx[x]=max(mx[x],sz[y]);
    }
}
void find_cen(int a,int x,int f){
    mx[x]=max(mx[x],sz[a]-sz[x]);
    if(rt==-1||mx[x]<=mx[rt])rt=x;
    for(int i=head[x];~i;i=e[i].nex){
        int y=e[i].to;
        if(y==f||vis[y])continue;
        find_cen(a,y,x);
    }
}
void up(int x,int f,int d,ll v){
    if(v<=m)Min(dp[v],d);
    for(int i=head[x];~i;i=e[i].nex){
        int y=e[i].to,val=e[i].v;
        if(y==f||vis[y])continue;
        if(v+val<=m)up(y,x,d+1,v+val);
    }
}
void rdfs(int x,int f,int d,ll val){
    if(val>m)return;
    for(int i=head[x];~i;i=e[i].nex){
        int y=e[i].to,v=e[i].v;
        if(vis[y]||f==y)continue;
        if(v+val<=m&&~dp[m-v-val]){
            Min(ans,d+dp[m-v-val]+1);
        }
        rdfs(y,x,d+1,val+v);
        if(x==rt)up(y,x,d+1,val+v);
    }
}
void clear(int x,int f,ll val){
    if(val>m)return;
    dp[val]=-1;
    for(int i=head[x];~i;i=e[i].nex){
        int y=e[i].to,v=e[i].v;
        if(vis[y]||f==y)continue;
        clear(y,x,val+v);
    }
}
void solve(int cur){
    dfs(cur,cur);
    rt=-1;
    find_cen(cur,cur,cur);
    dp[0]=0;
    rdfs(rt,rt,0,0);
    clear(rt,rt,0);
    vis[rt]=1;
    for(int i=head[rt];~i;i=e[i].nex){
        int y=e[i].to;
        if(vis[y])continue;
        solve(y);
    }
}
int main(){
    int i,j,k,a,b,c;
    memset(dp,-1,sizeof(dp));
    memset(head,-1,sizeof(head));
    rd(n);rd(m);
    for(i=1;i<n;i++){
        rd(a);rd(b);rd(c);
        ins(a,b,c);
    }
    solve(1);
    printf("%d\n",ans);
    return 0;
}

还有一种神奇的做法:启发式合并.
启发式合并的精髓在于把小集合并入大集合,每个元素所在的集合大小至少增大一倍,那么被合并的次数最多是logn次.
在这道题中:通过map将dis与dep相对应,作为一个集合的信息,合并的时候,对小集合进行询问和合并就可以完成问题.

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<queue>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<map>
#include<set>
#define ll long long 
#define y1 abcdedhf
#define pb push_back
using namespace std;
inline void rd(int &res){
    res=0;char c;
    while(c=getchar(),c<48);
    do res=(res<<1)+(res<<3)+(c^48);
    while(c=getchar(),c>=48);
}
void pt(int x){
    if(!x)return;
    pt(x/10);
    putchar((x%10)+'0');
}
void sc(int x){
    if(x<0){x=-x;putchar('-');}
    if(!x)putchar('0');
    pt(x);
    putchar('\n');
}
const int M=2e5+5;
const int S=1e6+5;
struct node{
    int to,v,nex;
}e[M<<1];
map<ll,int>f[M];
map<ll,int>*pos[M];
map<ll,int>::iterator it;
#define fi first
#define se second 
int n,head[M],ec=0,rt,m,ans=-1;//两千万 
inline void Min(int &x,int y){if(x==-1||x>y)x=y;}
void ins(int a,int b,int v){
    e[ec]=(node){b,v,head[a]};
    head[a]=ec++;
    e[ec]=(node){a,v,head[b]};
    head[b]=ec++;
}
void Merge(map<ll,int> *a,map<ll,int> *b,ll dis,int d){//把a并到b 
    for(it=a->begin();it!=a->end();it++){//dis[a]+dis[b]-2*dis=m  ->  dis[b]=m+2*dis-dis[a] 
        ll x=m-(it->fi)+2*dis;
        if(b->find(x)!=b->end()){
            Min(ans,it->se+(*b)[x]-2*d);//更新答案 
        }   
    }
    for(it=a->begin();it!=a->end();it++){
        ll x=it->fi;
        if(b->find(x)!=b->end()){
            (*b)[x]=min((*b)[x],it->se);
        }
        else (*b)[x]=it->se;
    } 
}
void dfs(int x,int par,ll dis,int d){
    f[x][dis]=d;
    pos[x]=&f[x];//pos[x]的地址为dp[] 
    for(int i=head[x];~i;i=e[i].nex){
        int y=e[i].to;
        if(y==par)continue;
        dfs(y,x,dis+e[i].v,d+1);
        if(pos[y]->size()>pos[x]->size()){//小并到大 
            Merge(pos[x],pos[y],dis,d);
            pos[x]->clear();
            pos[x]=pos[y];
        }
        else {
            Merge(pos[y],pos[x],dis,d);
            pos[y]->clear();
        }
    }
}
int main(){
    int i,j,k,a,b,c;
    memset(head,-1,sizeof(head));
    rd(n);rd(m);
    for(i=1;i<n;i++){
        rd(a);rd(b);rd(c);
        ins(a,b,c);
    }
    dfs(1,1,0,0);
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值