Description
n<=3000
O(n^3)
可以发现的结论:最优策略下,贡献形如选的k个点形成的虚树的边权和*2-选的k个点的直径
可以枚举直径,再树形背包,应该能弄到O(n^3)
O(n^2)
从直径入手
令
H[x][k]
表示 x 的子树内选择了 k 个点, 并且这 k 个点的直径两端都不是 x , 构成的虚树总边长的最小值 ×2− 直径的长度的最小值。
有了这个便能求出答案了,但是要怎么算这个呢?
思考一下,发现需要两个辅助数组F,G
G[x][k]
表示 x 的子树内选择了 k 个点, 并且这 k 个点包含了 x, 构成的虚树总边长的最小值。
F[x][k]
表示 x 的子树内选择了 k 个点, 并且这 k 个点的直径有一端是 x, 构成的虚树总边长的最小值 ×2− 直径的长度的最小值。
然后就在树上DP就可以了
这样子转移时的复杂度看起来是O(n^3)的,但实际上由于每对点只会在它们的LCA处被算一次,所以总的复杂度是O(n^2)的
Codes
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fd(i,b,a) for(int i=b;i>=a;--i)
#define efo(i,v,u) for(int i=BB[v],u=B[BB[v]][1];i;i=B[i][0],u=B[i][1])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
typedef double db;
char ch;
void read(int &n){n=0;int p=1;for(ch=getchar();ch<'0' || ch>'9';ch=getchar())if(ch=='-') p=-1;for(;'0'<=ch && ch<='9';ch=getchar()) n=n*10+ch-'0';n*=p;}
const int N=3005,INF=1e9;
int n,m,B0,B[N+N][3],BB[N];
void link(int u,int v,int w){B[++B0][1]=v,B[B0][0]=BB[u],B[B0][2]=w,BB[u]=B0;}
int ans,f[N][N],g[N][N],h[N][N];
void upd(int &x,int y){x=min(x,y);}
int dfs(int v,int fr=0)
{
f[v][1]=g[v][1]=0;
int sz=1;
efo(i,v,u) if(u!=fr)
{
int szu=dfs(u,v);
fd(j,min(m,sz),1)
fd(k,min(m,szu),1)
if(j+k<=m)
{
upd(h[v][j+k],f[v][j]+f[u][k]+B[i][2]);
upd(h[v][j+k],h[v][j]+2*(g[u][k]+B[i][2]));
upd(h[v][j+k],h[u][k]+2*(g[v][j]+B[i][2]));
upd(f[v][j+k],f[v][j]+2*(g[u][k]+B[i][2]));
upd(f[v][j+k],f[u][k]+B[i][2]+2*g[v][j]);
upd(g[v][j+k],g[v][j]+g[u][k]+B[i][2]);
}
sz+=szu;
}
upd(ans,f[v][m]);upd(ans,h[v][m]);
return sz;
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
int x,y,z;
read(n),read(m);
fo(i,1,n-1) read(x),read(y),read(z),link(x,y,z),link(y,x,z);
mset(f,60);mset(g,60);mset(h,60);
ans=INF;
dfs(1);
printf("%d",ans);
return 0;
}