@loj - [email protected] 「CTSC2016」时空旅行

目录

  • @[email protected]
  • @[email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

2045 年,人类的技术突飞猛进,已经找到了进行时空旅行的方法。小 R 得到了一台时空旅行仪,他想用它调查不同时空中人类的发展状况。

根据平行时空理论,宇宙中存在着很多独立的时空,每个时空在下一个时间点还会分化出若干个不同的时空。宇宙是一个三维空间,人类使用空间直角坐标系来描述空间中的一个位置,三维坐标分别是 x,y,z。

我们假设在初始的时空(编号为 0)中,人类存在于地球上(地球的坐标为 (0,0,0)),其他的时空都是从一个现有的时空发展而来的。一个时空发生一个时间之后会发展成为另外一个时空(原来的时空不发生任何变化)。会影响小 R 的时间包括两类:

人类殖民了一个新的星球,该星球的状态变成“已被殖民”。
人类放弃了一个已被殖民的星球,该星球的状态变成“未被殖民”。

每次进行时空旅行时,小 R 会先选定一个时空。在这个时空中,人类已经殖民了一些星球。小 R 只要到达该时空中任意一个已被殖民的星球,就能调查人类的发展状况。

小 R 的时空旅行仪出现了一些问题,调整 x 坐标的按钮坏掉了,因此到达点的 x 坐标被固定了(每次旅行的 x 坐标值可能不同)。与此同时,他仍能任意调整到达点的 y 坐标和 z 坐标。

这个问题大大增大了小 R 的花费:因为时空旅行没有花费,但在太空中航行却需要花钱;同时,在不同星球进行调查也可能会产生不同的费用。

假设小 R 将时空旅行的终点设为 A,他要进行调查的星球为 B:如果 A 与 B 的欧几里得距离为 d,那么他太空航行的花费就是 d^2;又如果星球 B 上进行调查的费用为 c,那么小 R 此次调查的总花费就是 d^2 + c。

现在给定小 R 每次旅行到达的时空以及时空旅行仪上固定的 x 坐标值,请你计算出小 R 每次旅行完成调查的最小总花费。

原题传送门。

@[email protected]

首先不难发现 y, z 都是来唬你的,其实代价函数为 (xi - x0)^2 + ci;接着你发现代价函数是个明显的斜率优化。

考虑一个点的影响范围:它第一次被加入的位置对应的子树 T,扣掉它被删除的位置集合 S 对应的子树集合 Ts。
可以用 dfs 序把影响范围拆解成若干区间。如果该点被删除了 x 次,则会产生最多 x + 1 个区间。

注意到一个树点恰好对应增/删一次,所以拆解出来的区间总数是 O(n) 的。

那么问题转化成:首先区间加点,然后单点询问凸包。
如果我们把 dfs 序看成从左往右的时间轴,则可以变成凸包加/删点,给定斜率进行询问。
凸包并不能删点(李超线段树也不行),所以考虑一个不用能把删除去掉辅助的算法,比如线段树分治(当然这一步并不是必需的转化,你可以直接联想到线段树)。

使用线段树分治后,线段树上每一个结点有着若干点以及若干询问。
如果使用在每个结点内部使用 O(nlogn) 的算法,总复杂度为 O(nlog^2n)。

考虑在最外层排好序后再插入到线段树中,这样最终线段树上每一个结点内部的点集与询问都是有序的,直接单调队列可以做到 O(n)。
因此总复杂度 O(nlogn)。

@accepted [email protected]

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

const int MAXN = 500000;
const double INF = 1E18;

typedef long long ll;

ll ans[MAXN + 5]; int n, m;

struct point{
    ll x, y; point() {}
    point(ll _x, ll _y) : x(_x), y(_y) {}
    friend bool operator < (const point &a, const point &b) {
        return (a.x == b.x ? a.y < b.y : a.x < b.x);
    }
};
struct query{
    ll k; int id, pos; query() {}
    query(ll _k, int _i) : k(_k), id(_i) {}
    friend bool operator < (const query &a, const query &b) {
        return a.k < b.k;
    }
}b[MAXN + 5];
struct node{
    int l, r; point p; node() {}
    node(int _l, int _r, point _p) : l(_l), r(_r), p(_p) {}
    friend bool operator < (const node &a, const node &b) {
        return a.p < b.p;
    }
}a[2*MAXN + 5]; int acnt;

#define lch (x << 1)
#define rch (x << 1 | 1)
int le[4*MAXN + 5], ri[4*MAXN + 5];
vector<int>v1[4*MAXN + 5], v2[4*MAXN + 5];
void build(int x, int l, int r) {
    le[x] = l, ri[x] = r;
    if( l == r ) return ;
    int m = (l + r) >> 1;
    build(lch, l, m), build(rch, m + 1, r);
}
void insert(int x, int l, int r, int k) {
    if( r < le[x] || l > ri[x] )
        return ;
    if( l <= le[x] && ri[x] <= r ) {
        v1[x].push_back(k);
        return ;
    }
    insert(lch, l, r, k), insert(rch, l, r, k);
}
void add(int x, int p, int k) {
    v2[x].push_back(k);
    if( le[x] == ri[x] ) return ;
    int m = (le[x] + ri[x]) >> 1;
    add(p <= m ? lch : rch, p, k);
}
point que[MAXN + 5];
double slope(point a, point b) {
    if( a.x == b.x )
        return a.y < b.y ? INF : -INF;
    else return 1.0 *(a.y - b.y) / (a.x - b.x);
}
void get(int x) {
    int s = 1, t = 0;
    for(int i=0;i<v1[x].size();i++) {
        point p = a[v1[x][i]].p;
        if( s <= t && que[t].x == p.x && que[t].y == p.y ) continue;
        while( s < t && slope(que[t - 1], que[t]) >= slope(que[t], p) )
            t--;
        que[++t] = p;
    }
    for(int i=0;i<v2[x].size();i++) {
        int id = b[v2[x][i]].id; ll k = b[v2[x][i]].k;
        while( s < t && slope(que[s], que[s + 1]) <= k )
            s++;
        if( s <= t ) ans[id] = min(ans[id], que[s].y - k*que[s].x + k*k);
    }
}
void solve(int x) {
    get(x);
    if( le[x] == ri[x] ) return ;
    solve(lch), solve(rch);
}

int x[MAXN + 5]; ll c[MAXN + 5];
point pnt(int i) {
    return point(2*x[i], 1LL*x[i]*x[i] + c[i]);
}

struct edge{
    int to; edge *nxt;
}edges[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;
}
bool type[MAXN + 5]; int id[MAXN + 5], fa[MAXN + 5];
int dfn[MAXN + 5], tid[MAXN + 5], dcnt;
int nw[MAXN + 5];
void dfs(int x) {
    tid[x] = dcnt, dfn[dcnt++] = x;
    if( !type[x] ) nw[id[x]] = tid[x];
    else a[++acnt] = node(nw[id[x]], tid[x] - 1, pnt(id[x]));
    for(edge *p=adj[x];p;p=p->nxt) {
        dfs(p->to);
    }
    if( !type[x] ) a[++acnt] = node(nw[id[x]], dcnt - 1, pnt(id[x]));
    else nw[id[x]] = dcnt;
}

int readi() {
    int x = 0, f = 1; int ch = getchar();
    while( (ch != '-') && (ch > '9' || ch < '0') ) ch = getchar();
    if( ch == '-' ) f = -1, ch = getchar();
    while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
    return x * f;
}
ll readl() {
    ll x = 0; int ch = getchar();
    while( ch > '9' || ch < '0' ) ch = getchar();
    while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
    return x;
}
void write(ll x) {
    if( !x ) return ;
    write(x / 10);
    putchar(x % 10 + '0');
}
int main() {
    freopen("travel.in", "r", stdin);
    freopen("travel.out", "w", stdout);

    n = readi(), m = readi(), c[0] = readl(), type[0] = id[0] = 0;
    for(int i=1;i<n;i++) {
        type[i] = readi(), fa[i] = readi(), id[i] = readi();
        if( type[i] == 0 )
            x[id[i]] = readi(), readi(), readi(), c[id[i]] = readl();
        addedge(fa[i], i);
    }
    build(1, 0, n - 1), dfs(0);
    sort(a + 1, a + acnt + 1);
    for(int i=1;i<=acnt;i++)
        insert(1, a[i].l, a[i].r, i);
    for(int i=1;i<=m;i++) {
        int s = readi(), x0 = readi();
        ans[i] = INF, b[i] = query(x0, i), b[i].pos = tid[s];
    }
    sort(b + 1, b + m + 1);
    for(int i=1;i<=m;i++)
        add(1, b[i].pos, i);
    solve(1);
    for(int i=1;i<=m;i++)
        write(ans[i]), puts("");
}

@[email protected]

没事儿不要在这种大数据范围情况下用 vector。

第一次直接存点和询问用 vector 结果 MLE 了,后来改成存点和询问的编号虽然 AC 了,但是常数还是比较大。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12408531.html

时间: 2024-11-03 01:29:43

@loj - [email protected] 「CTSC2016」时空旅行的相关文章

@loj - [email&#160;protected]「CEOI2017」Building Bridges

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 有 n 根柱子依次排列,第 i 根柱子的高度为 hi .现可以花费 (hi - hj)^2 的代价建桥架在第 i 根柱子和第 j 根柱子之间. 所有用不到的柱子都会被拆除,第 i 根柱子被拆除的代价为 wi . 求用桥把第 1 根柱子和第 n 根柱子连接的最小代价.注意桥梁不能在端点以

@loj - [email&#160;protected]「SDOI2014」Lis

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定序列 A,序列中的每一项 Ai 有删除代价 Bi 和附加属性 Ci 请删除若干项,使得 A 的最长上升子序列长度减少至少 1,且付出的代价之和最小,并输出方案. 如果有多种方案,请输出将删去项的附加属性排序之后,字典序最小的一种. 输入格式 输入包含多组数据. 输入的第一行包含整数

@loj - [email&#160;protected] 「CQOI2017」老 C 的方块

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 老 C 是个程序员. 作为一个懒惰的程序员,老 C 经常在电脑上玩方块游戏消磨时间.游戏被限定在一个由小方格排成的 R 行 C 列网格上,如果两个小方格有公共的边,就称它们是相邻的,而且有些相邻的小方格之间的公共边比较特殊. 特殊的公共边排列得有很强的规律.首先规定,第 1 行的前两个

@loj - [email&#160;protected] 「SDOI2017」硬币游戏

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 周末同学们非常无聊,有人提议,咱们扔硬币玩吧,谁扔的硬币正面次数多谁胜利. 大家纷纷觉得这个游戏非常符合同学们的特色,但只是扔硬币实在是太单调了. 同学们觉得要加强趣味性,所以要找一个同学扔很多很多次硬币,其他同学记录下正反面情况. 用 H 表示正面朝上, 用 T 表示反面朝上,扔很多

@loj - [email&#160;protected] 「清华集训 2017」生成树计数

目录 @[email protected] @[email protected] @正文@ @补充@ @accepted [email protected] @[email protected] @[email protected] 在一个 s 个点的图中,存在 s - n 条边,使图中形成了 n 个连通块,第 i 个连通块中有 \(a_i\) 个点. 现在我们需要再连接 n - 1 条边,使该图变成一棵树.对一种连边方案,设原图中第 i 个连通块连出了 \(d_i\) 条边,那么这棵树 T 的

uoj198【CTSC2016】时空旅行

传送门:http://uoj.ac/problem/198 [题解] 首先y.z是没有用的.. 然后式子就是w = (x0-xi)^2+ci的最小值,化出来可以变成一个直线的形式. 然后我们可以用线段树维护dfs序上的每个点. 每个点维护经过这个点的所有直线(标记永久化),也就是维护上凸壳. 然后我们把询问按照x排序,每次决策点只会后移.所以复杂度就有保证啦! 真**难写 还有一个十分有趣的事实啊 我用一个号交完ac在另一个号再交就RE了啊... 不管了反正过了 # include <vecto

loj#2552. 「CTSC2018」假面

题目链接 loj#2552. 「CTSC2018」假面 题解 本题严谨的证明了我菜的本质 对于砍人的操作好做找龙哥就好了,blood很少,每次暴力维护一下 对于操作1 设\(a_i\)为第i个人存活的概率,\(d_i\)为死掉的概率,\(g_{i,j}\)是除i以外活了j个人的概率 那个选中i人的答案就是 \[a_i\times\sum_{j = 0} ^{k - 1}\frac{g_{i,j}}{j + 1}\] 对于\(g_{i,j}\) ,设\(f_{i,j}\)表示前\(i\)个人有\(

loj#2076. 「JSOI2016」炸弹攻击 模拟退火

目录 题目链接 题解 代码 题目链接 loj#2076. 「JSOI2016」炸弹攻击 题解 模拟退火 退火时,由于答案比较小,但是温度比较高 所以在算exp时最好把相差的点数乘以一个常数让选取更差的的概率降低 代码 #include<ctime> #include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #define gc getchar() #define

Loj #2541「PKUWC2018」猎人杀

Loj #2541. 「PKUWC2018」猎人杀 题目链接 好巧妙的题! 游戏过程中,概率的分母一直在变化,所以就非常的不可做. 所以我们将问题转化一下:我们可以重复选择相同的猎人,只不过在一个猎人被选择了过后我们就给他打上标记,再次选择他的时候就无效.这样与原问题是等价的. 证明: 设\(sum=\sum_iw_i,kill=\sum_{i被杀死了}w_i\). 攻击到未被杀死的猎人\(i\)的概率为\(P\). 则根据题意\(P=\frac{w_i}{sum-kill}\). 问题转化后: