`求在树上长为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;
}