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;
}