• @codeforces



    @description@

    给定一个 n 个点的树(标号1~n),以结点 1 为根。每个结点有两个点权 ai 与 bi。

    你可以从一个点出发跳到它的子树中的某个结点去(不能跳到自己)。
    从 x 跳到 y 所花费的代价为 ax * by,跳跃的总代价为每次跳跃的代价之和。

    对于每个结点,计算从它出发跳到某一叶子结点的最小代价和。

    Input
    第一行包含一个整数 n (2 ≤ n ≤ 10^5),表示树中的结点数量。
    第二行包含 n 个整数 a1, a2, ..., an (-10^5≤ai≤10^5)。
    第三行包含 n 个整数 b1, b2, ..., bn (-10^5≤bi≤10^5)。
    接下来 n-1 行每行包含两个整数 ui 和 vi (1≤ui, vi≤n),描述了树中的一条边。

    Output
    输出 n 个空格分开的整数,第 i 个描述了从第 i 个结点跳到叶子结点的最小代价和。

    Examples
    Input
    3
    2 10 -1
    7 -7 5
    2 3
    2 1
    Output
    10 50 0

    Input
    4
    5 -10 5 7
    -8 -80 -3 -10
    2 1
    2 4
    1 3
    Output
    -300 100 0 0

    @solution@

    本题方法很多,可以转成 dfs 序然后写 cdq 分治,可以写平衡树在树上启发式合并维护凸包,也可以像我一开始一样转成 dfs 序分块维护凸包(它竟然没有 TLE。。。令我颇感意外)。
    在这里介绍一个不那么传统的方法吧:我们使用李超树 + 线段树合并。

    首先不难写出 dp 式 dp[x] = min(dp[c] + ax*bc),发现它是斜率优化的形式。
    处理斜率优化问题,除了传统的方法将其看作凸包以外,其实还有李超线段树的方法。
    我们记 bc 为斜率,dp[c] 为截距,ax 为横坐标,可以通过李超线段树找到最小值(可自行百度)。

    类比于平衡树的启发式合并,我们可以直接用经典的线段树合并,在树上对李超线段树进行合并操作。
    与平常的线段树合并不同的是,当两棵线段树的结点同时都含有直线标记,要将一个直线标记插入到另一棵线段树中。

    复杂度感觉像是 O(nlogn)?每个直线标记最多在线段树中被下放 log 次,而线段树合并的复杂度 <= 一个一个将结点插入的复杂度。
    但是感觉跑起来没有 O(nlogn) 那么快?不是很清楚是常数问题还是时间复杂度证错了。

    @accepted code@

    #include <cmath>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const int MAXN = 100000;
    const ll INF = (1LL<<60);
    struct line{
    	ll k, b;
    	line(ll _k=0, ll _b=0) : k(_k), b(_b) {}
    	ll get(ll x) {return k * x + b;}
    };
    ll a[MAXN + 5], b[MAXN + 5], f[MAXN + 5];
    struct segtree{
    	struct node{
    		line l;
    		node *ch[2];
    	}pl[60*MAXN + 5], *ncnt, *NIL;
    	segtree() {
    		NIL = ncnt = pl;
    		NIL->ch[0] = NIL->ch[1] = NIL;
    	}
    	node *newnode(line l) {
    		node *p = (++ncnt);
    		p->ch[0] = p->ch[1] = NIL, p->l = l;
    		return p;
    	}
    	void insert(node *&rt, int l, int r, line ln) {
    		if( rt == NIL ) {
    			rt = newnode(ln);
    			return ;
    		}
    		int m = (int)floor(1.0*(l + r)/2);
    		if( rt->l.get(l) > ln.get(l) ) swap(rt->l, ln);
    		if( rt->l.get(m) > ln.get(m) )
    			swap(rt->l, ln), insert(rt->ch[0], l, m, ln);
    		else if( rt->l.get(r) > ln.get(r) )
    			insert(rt->ch[1], m + 1, r, ln);
    	}
    	ll query(node *rt, int l, int r, ll p) {
    		if( rt == NIL ) return INF;
    		if( l == r ) return rt->l.get(p);
    		int m = (int)floor(1.0*(l + r)/2);
    		if( p <= m ) return min(rt->l.get(p), query(rt->ch[0], l, m, p));
    		else return min(rt->l.get(p), query(rt->ch[1], m + 1, r, p));
    	}
    	node *merge(node *rt1, node *rt2, int l, int r) {
    		if( rt1 == NIL ) return rt2;
    		if( rt2 == NIL ) return rt1;
    		int m = (int)floor(1.0*(l + r)/2);
    		rt1->ch[0] = merge(rt1->ch[0], rt2->ch[0], l, m);
    		rt1->ch[1] = merge(rt1->ch[1], rt2->ch[1], m + 1, r);
    		insert(rt1, l, r, rt2->l);
    		return rt1;
    	}
    }T;
    struct edge{
    	int to; edge *nxt;
    }edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = edges;
    void addedge(int u, int v) {
    	edge *p = (++ecnt);
    	p->to = v, p->nxt = adj[u], adj[u] = p;
    	p = (++ecnt);
    	p->to = u, p->nxt = adj[v], adj[v] = p;
    }
    segtree::node *rt[MAXN + 5];
    void dfs(int x, int fa) {
    	rt[x] = T.NIL;
    	for(edge *p=adj[x];p;p=p->nxt)
    		if( p->to != fa )
    			dfs(p->to, x), rt[x] = T.merge(rt[x], rt[p->to], -MAXN, MAXN);
    	if( rt[x] == T.NIL ) f[x] = 0;
    	else f[x] = T.query(rt[x], -MAXN, MAXN, a[x]);
    	T.insert(rt[x], -MAXN, MAXN, line(b[x], f[x]));
    }
    int main() {
    	int n; scanf("%d", &n);
    	for(int i=1;i<=n;i++) scanf("%lld", &a[i]);
    	for(int i=1;i<=n;i++) scanf("%lld", &b[i]);
    	for(int i=1;i<n;i++) {
    		int u, v; scanf("%d%d", &u, &v);
    		addedge(u, v);
    	}
    	dfs(1, 0);
    	for(int i=1;i<=n;i++) printf("%lld%c", f[i], i == n ? '
    ' : ' ');
    }
    

    @details@

    现在只写了分块维护凸包与李超线段树合并两种方法。

    有时间(咕咕咕)练一下另外两种吧。

  • 相关阅读:
    浅析CString内部实现机制
    ...sourceannotations.h(142) : error C3094: “repeatable”: 不允许匿名使用
    非MFC项目使用CString及如何打印
    GetTextExtentPoint32--获取字符串在屏幕上长度
    窄字符与宽字符相关的操作
    如何给图片添加黑色边框
    react native
    礼仪 习俗 文化
    职业 行业 2 博客
    读书 文摘 笔记 2 人生的支柱
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11862009.html
Copyright © 2020-2023  润新知