【题解】ZOJ More Health Points

点此处题目链接

题目大意:
给出一个有 n 个节点的树,边是有向的,由孩子指向父亲,每个点 u 都有一个值 bu。作为玩家的你可以任选一个点进入,从你能到达的任意一个点退出(包括进入的点,你甚至可以不进入)。假设你达到 u 点后又过了 tu-1 个点才退出,那么你获得的分数是 ∑ u 是 你 走 过 的 点 t u ⋅ b u \sum_{u是你走过的点}t_u\cdot b_u utubu。问你能获得的最大分数。


法一:斜率优化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]=depjbj,起点是 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] depvs[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=depvs[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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值