标签:mat printf 最小花费 一条直线 through 平面 ++i min span
我们设 \(dp_i\) 表示编号为 \(i\) 的节点到达该树叶子节点的最小花费,那么显然我们有以下的转移方程:
直接转移是 \(\text{O}\left(n^2\right)\) 的,考虑如何优化。
我们令 \(dp_j+a_i \times b_j\) 为 \(y\) ,\(a_i\) 为 \(x\) ,则我们可以在二维平面上用 \(y=b_j \times x + dp_j\) 这条直线表示出一种转移的情况,那么我们求 \(\min\left\{dp_j+a_i \times b_j\right\}\) 等价于在若干条直线找到一条直线,使其与 \(x=a_i\) 交点的纵坐标最小,因此可以直接使用李超树维护。
可以先将树的 dfs 序处理出来,那么 dp 的转移便是一个区间,因此线段树套李超树维护一下就行了。
const int N=4e5+5;
int n,a[N],b[N],dfn[N],low[N],ans[N],cnt;
vector<int> val,lcc,rcc;
struct Edge
{
int v,ne;
}e[N*2];
int head[N],tot;
struct Line
{
int k,b;
}c[N];
inline int f(int id,int v);
inline void add(int u,int v);
void dfs1(int u,int fa);
void dfs2(int u,int fa);
void build(int x,int l,int r);
void update1(int x,int l,int r,int p,int v);
int query1(int x,int l,int r,int L,int R,int v);
void update2(int x,int l,int r,int v);
int query2(int x,int l,int r,int v);
signed main()
{
n=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) b[i]=read();
for(int i=1;i<=n-1;++i)
{
int u=read(),v=read();
add(u,v);add(v,u);
}
c[0].b=1e18;
dfs1(1,0);
cnt=0;build(1,1,n);
for(int i=0;i<=cnt;++i)
{
val.push_back(0);
lcc.push_back(0);
rcc.push_back(0);
}
dfs2(1,0);
for(int i=1;i<=n;++i)
printf("%lld ",ans[i]);
return 0;
}
inline int f(int id,int v)
{
return c[id].k*v+c[id].b;
}
inline void add(int u,int v)
{
e[++tot]=(Edge){v,head[u]};
head[u]=tot;
}
void dfs1(int u,int fa)
{
dfn[u]=++cnt;
for(int i=head[u];i;i=e[i].ne)
{
int v=e[i].v;
if(v==fa) continue;
dfs1(v,u);
}
low[u]=cnt;
}
void dfs2(int u,int fa)
{
int fl=1;
for(int i=head[u];i;i=e[i].ne)
{
int v=e[i].v;
if(v==fa) continue;
dfs2(v,u);fl=0;
}
if(fl) ans[u]=0;
else ans[u]=query1(1,1,n,dfn[u]+1,low[u],a[u]+100001);
c[u]=(Line){b[u],ans[u]};
update1(1,1,n,dfn[u],u);
}
void build(int x,int l,int r)
{
cnt=max(cnt,x);
if(l==r) return;
build(lc,l,mid);
build(rc,mid+1,r);
}
void update1(int x,int l,int r,int p,int v)
{
update2(x,1,200001,v);
if(l==r) return;
if(p<=mid) update1(lc,l,mid,p,v);
else update1(rc,mid+1,r,p,v);
}
int query1(int x,int l,int r,int L,int R,int v)
{
if(L<=l&&r<=R) return query2(x,1,200001,v);
int res=1e18;
if(L<=mid) res=min(res,query1(lc,l,mid,L,R,v));
if(mid+1<=R) res=min(res,query1(rc,mid+1,r,L,R,v));
return res;
}
void update2(int x,int l,int r,int v)
{
if(l==r)
{
if(f(v,l-100001)<f(val[x],l-100001))
val[x]=v;
return;
}
if(c[v].k>c[val[x]].k)
{
if(f(v,mid-100001)<f(val[x],mid-100001))
{
if(!rcc[x])
{
rcc[x]=++cnt;
val.push_back(0);
lcc.push_back(0);
rcc.push_back(0);
}
update2(rcc[x],mid+1,r,val[x]),val[x]=v;
}
else
{
if(!lcc[x])
{
lcc[x]=++cnt;
val.push_back(0);
lcc.push_back(0);
rcc.push_back(0);
}
update2(lcc[x],l,mid,v);
}
}
if(c[v].k<c[val[x]].k)
{
if(f(v,mid-100001)<f(val[x],mid-100001))
{
if(!lcc[x])
{
lcc[x]=++cnt;
val.push_back(0);
lcc.push_back(0);
rcc.push_back(0);
}
update2(lcc[x],l,mid,val[x]);val[x]=v;
}
else
{
if(!rcc[x])
{
rcc[x]=++cnt;
val.push_back(0);
lcc.push_back(0);
rcc.push_back(0);
}
update2(rcc[x],mid+1,r,v);
}
}
if(c[v].k==c[val[x]].k)
{
if(f(v,mid-100001)<f(val[x],mid-100001))
val[x]=v;
return;
}
}
int query2(int x,int l,int r,int v)
{
if(x==0) return 1e18;
int res=f(val[x],v-100001);
if(l==r) return res;
if(v<=mid) return min(res,query2(lcc[x],l,mid,v));
else return min(res,query2(rcc[x],mid+1,r,v));
}
标签:mat printf 最小花费 一条直线 through 平面 ++i min span
原文地址:https://www.cnblogs.com/blackbird137/p/14402433.html