IOI2011 race

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;
}
發佈了39 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章