点此处题目链接
题目大意:
给出一个有 n 个节点的树,边是有向的,由孩子指向父亲,每个点 u 都有一个值 bu。作为玩家的你可以任选一个点进入,从你能到达的任意一个点退出(包括进入的点,你甚至可以不进入)。假设你达到 u 点后又过了 tu-1 个点才退出,那么你获得的分数是
∑
u
是
你
走
过
的
点
t
u
⋅
b
u
\sum_{u是你走过的点}t_u\cdot b_u
∑u是你走过的点tu⋅bu。问你能获得的最大分数。
法一:斜率优化dp+可持久化单调栈
易知玩家只能走某条链上的某段区间,因此先考虑单独一条链的情况。
设 i 点的深度是
d
e
p
i
dep_i
depi,
s
[
i
]
=
∑
b
j
s[i]=\sum b_j
s[i]=∑bj,
s
s
[
i
]
=
∑
d
e
p
j
∗
b
j
ss[i]=\sum dep_j*b_j
ss[i]=∑depj∗bj,起点是 u ,终点的父节点是 v ,分数是 dp[u] 。那么可以得到
d
p
[
u
]
=
s
s
[
u
]
−
s
s
[
v
]
−
d
e
p
v
(
s
[
u
]
−
s
[
v
]
)
dp[u]=ss[u]-ss[v]-dep_v(s[u]-s[v])
dp[u]=ss[u]−ss[v]−depv(s[u]−s[v])
整理得
d
e
p
v
⋅
s
[
v
]
−
s
s
[
v
]
=
s
[
u
]
⋅
d
e
p
v
+
d
p
[
u
]
−
s
s
[
u
]
dep_v\cdot s[v]-ss[v]=s[u]\cdot dep_v+dp[u]-ss[u]
depv⋅s[v]−ss[v]=s[u]⋅depv+dp[u]−ss[u]
考虑斜率优化,令
y
=
d
e
p
v
⋅
s
[
v
]
,
x
=
d
e
p
v
y=dep_v\cdot s[v], x=dep_v
y=depv⋅s[v],x=depv,得到
y
=
s
[
u
]
⋅
x
+
d
p
[
u
]
−
s
s
[
u
]
y=s[u]\cdot x+dp[u]-ss[u]
y=s[u]⋅x+dp[u]−ss[u]
我们可以二分得 dp[u],单调栈维护凸壳即可。
对于树,我们把单调栈可持久化即可。
复杂度 O(nlogn)。
谁能告诉我为什么更新单调栈时不二分就TLE啊 qwq
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1e5+10;
LL b[N], s[N], ss[N], cc[N];
LL ans;
//Edge
struct Node
{
int to, next;
}e[N];
int hn, h[N];
void add(int u, int v)
{
e[++hn].to=v; e[hn].next=h[u];
h[u]=hn;
}
//slope
int q[N], head, tail;
LL x0[N], y0[N];
void calc(int l, int r, LL k, int u)
{
int mid; LL res;
while(l<r)
{
mid=l+r>>1;
if(y0[q[mid]]-y0[q[mid-1]]<=k*(x0[q[mid]]-x0[q[mid-1]])) r=mid;
else l=mid;
if(l+1==r) break;
}
if((y0[q[r]]-y0[q[r-1]])>=k*(x0[q[r]]-x0[q[r-1]])) res=r;
else res=l;
res=q[res]; res=y0[res]-s[u]*x0[res]+ss[u];
ans=max(ans,res);
}
bool judge(int a, int b, int c)
{
return (y0[a]-y0[c])*(x0[a]-x0[b])>(y0[a]-y0[b])*(x0[a]-x0[c]);
}
int calc2(int l, int r, int u)
{
int mid, res;
while(l<r)
{
mid=l+r>>1;
if(judge(q[mid],q[mid+1],u)) r=mid;
else l=mid;
if(l+1==r) break;
}
if(judge(q[l],q[l+1],u)) return l;
return r;
}
void dfs(int u, int fa, int c)
{
cc[u]=c;
s[u]=s[fa]+b[u];
ss[u]=ss[fa]+b[u]*c;
x0[u]=cc[u]; y0[u]=cc[u]*s[u]-ss[u]; // 计算 x 和 y
calc(head,tail-1,s[u],u); // 二分,更新答案
int oldt=tail, pos=calc2(head,tail-1,u)+1, old=q[pos]; tail=pos; // 可持久化:记录修改前的版本
q[tail++]=u;
for(int p=h[u]; p; p=e[p].next) dfs(e[p].to,u,c+1);
q[tail-1]=old; tail=oldt; // 撤销修改
}
void solve()
{
hn=0; memset(h,0,sizeof(h));
memset(q,0,sizeof(q));
ans=0ll; head=tail=0;
int n; scanf("%d", &n);
for(int i=1; i<=n; ++i) scanf("%lld", b+i);
for(int i=2, x; i<=n; ++i)
{
scanf("%d", &x);
add(x,i);
}
q[tail++]=0;
dfs(1,0,1);
printf("%lld\n", ans);
}
int main()
{
int T; cin>>T;
while(T--) solve();
return 0;
}
法二:树分治+斜率优化
按照树分治一贯的做法,在以重心为根结点的子树中,找经过根结点的所有路径的最大值。路径分成两部分,向标号大的点的路径+向标号小的点的路径。可以用斜率优化,复杂度据说是 O(nlog2n) 。没上一个快。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1e5+10;
int n;
LL b[N], ans;
/// Edge
struct Edge
{
int to, next;
void set(int to=0, int next=0){ this->to=to, this->next=next; }
}e[N<<1];
int hn, h[N];
void add(int u, int v)
{
e[++hn].set(v,h[u]);
h[u]=hn;
}
/// get order
int ord[N];
void dfs1(int u, int fa, int d)
{
ord[u]=d;
for(int p=h[u]; p; p=e[p].next)
{
int v=e[p].to;
if(v==fa) continue;
dfs1(v,u,d+1);
}
}
///***divide and conquer
bool vis[N];
int sz[N], msz[N], dep[N], q[N], qn;
///get centre
void dfs21(int u, int fa)
{
q[++qn]=u;
sz[u]=1; msz[u]=0;
for(int p=h[u]; p; p=e[p].next)
{
int v=e[p].to;
if(v==fa||vis[v]) continue;
dfs21(v,u);
sz[u]+=sz[v];
msz[u]=max(msz[u],sz[v]);
}
}
int get_centre(int u)
{
qn=0; dfs21(u,0);
int siz=sz[u], res, minsz=siz+1;
for(int i=1; i<=qn; ++i)
{
int v=q[i];
int tmp=max(msz[v],siz-sz[v]);
if(tmp<minsz)
{
minsz=tmp;
res=v;
}
}
return res;
}
/// get res
LL ss[N], s[N];
int sta[N], top;
void dfs22(int u, int fa, int d)
{
q[++qn]=u; dep[u]=d;
s[u]+=b[u]; ss[u]+=b[u]*(d-1);
for(int p=h[u]; p; p=e[p].next)
{
int v=e[p].to;
if(v==fa||vis[v]) continue;
s[v]=s[u]; ss[v]=ss[u];
dfs22(v,u,d+1);
}
}
void find_up(int u, int fa)
{
q[++qn]=u;
for(int p=h[u]; p; p=e[p].next)
{
int v=e[p].to;
if(v==fa||vis[v]||ord[v]>=ord[u]) continue;
find_up(v,u);
}
}
///slope
#define x(u) (dep[u])
#define y(u) (s[u]*dep[u]-ss[u])
bool check1(int a, int b, int c)
{
return (y(a)-y(c))*(x(a)-x(b))>(y(a)-y(b))*(x(a)-x(c));
}
bool check2(int a, int b, LL k)
{
return (y(b)-y(a))>k*(x(b)-x(a));
}
int get_tmp(LL k)
{
int l=0, r=top-1, mid;
while(l<r)
{
mid=l+r>>1;
if(check2(sta[mid],sta[mid+1],k)) l=mid;
else r=mid;
if(l+1==r) break;
}
if(check2(sta[l],sta[r],k)) return sta[r];
return sta[l];
}
void dfs23(int u, int fa, LL bu)
{
LL k=-(bu+s[u]);
int tmp=get_tmp(k);
ans=max(ans,y(tmp)-k*x(tmp)+ss[u]);
for(int p=h[u]; p; p=e[p].next)
{
int v=e[p].to;
if(v==fa||vis[v]||ord[v]<=ord[u]) continue;
dfs23(v,u,bu);
}
}
void cal_res(int u)
{
qn=s[u]=ss[u]=0; dfs22(u,0,1);
for(int i=1; i<=qn; ++i) s[q[i]]-=b[u];
qn=0; find_up(u,0);
top=0; sta[top++]=u;
for(int i=2; i<=qn; ++i)
{
while(top>1&&check1(sta[top-2],sta[top-1],q[i])) top--;
sta[top++]=q[i];
}
dfs23(u,0,b[u]);
}
void dfs2(int u)
{
int w=get_centre(u);
cal_res(w);
vis[w]=true;
for(int p=h[w]; p; p=e[p].next)
{
int v=e[p].to;
if(vis[v]) continue;
dfs2(v);
}
}
void solve()
{
ans=0; hn=0;
memset(h,0,sizeof(h));
memset(vis,false,sizeof(vis));
scanf("%d", &n);
for(int i=1; i<=n; ++i) scanf("%lld", b+i);
for(int i=2; i<=n; ++i)
{
int x;
scanf("%d", &x);
add(x,i); add(i,x);
}
dfs1(1,0,1);
dfs2(1);
printf("%lld\n", ans);
}
int main()
{
int T; cin>>T;
while(T--) solve();
return 0;
}
788

被折叠的 条评论
为什么被折叠?



