首页 > 其他分享 >[JOISC 2021 Day3] 保镖 解题报告

[JOISC 2021 Day3] 保镖 解题报告

时间:2023-06-01 22:36:11浏览次数:55  
标签:保镖 int ll Day3 JOISC tag 2021 id dp

statement

给定 \(n\) 个人,每个人从 \(T_i\) 秒开始从 \(a_i\) 移动到 \(b_i\),每秒移动一个单位。给定 \(q\) 个保镖,每个保镖从 \(P_i\) 秒开始,从 \(x_i\) 开始移动,每秒一个单位。如果保镖和人在同一个位置上,就可以获得 \(C_i\) 的奖金,问每个保镖最多能获得多少奖金。

analysis

考虑到人不多,先对所有人的位置进行离散化。我们可以尝试用 \((\text{时间},\text{坐标})\) 的二维平面刻画每一个人。那么对于任意一个人的行动轨迹应该是一条与 \(y=x\) 或 \(y=-x\) 平行的直线。而保镖的图像也是由若干这样的直线拼接而成的。

注意到所有直线之间的夹角均为 \(90^\circ\),我们可以将所有直线顺时针翻转 \(45^\circ\),使其与坐标轴平行。

平面直角坐标系中,将 \((x,y)\) 翻转 \(\theta\) 度(逆时针)的坐标可以由如下矩阵得到:

\(\begin{bmatrix}\cos\theta&-\sin\theta\\\sin\theta&\cos\theta \end{bmatrix}\begin{bmatrix}x \\ y\end{bmatrix}\)

可以从向量的角度证明此结论。

则 \((x,y)\) 翻转 \(45^\circ\) 得到的是 \((\frac{\sqrt 2}{2}x-\frac{\sqrt 2}{2}y,\frac{\sqrt 2}{2}x + \frac{\sqrt 2}{2}y)\)

由于 \(\sqrt 2\) 在计算机中并不好处理,我们将所有的线段长度都扩大至 \(\sqrt 2\) 倍,那么得到的坐标就是 \((x-y,x+y)\)。

那么现在在新矩形中一条边的长度是走过路径长的两倍(在原来的二维平面中由直边转为斜边,这里又翻了 \(\sqrt 2\) 倍)。

现在每个人可以用一条竖着的线段或横着的线段表示。

考虑保镖只能向上和向右走,那么 dp 可以解决,然而保镖的数量非常大,要换一种考虑方式。

设 \(f_{i,j}\) 表示从 \((i,j)\) 开始向上和向右走可以获得的最大收益,保镖可以分为这样两段路:

  1. 从自身的起点开始走到自己右上方的一个已经求出 \(f\) 的起点
  2. 从 \((i,j)\) 走出去

可以预处理出 \(f_{i,j}\),时间复杂度 \(\mathcal O(n^2)\)。

那么问题转化为如何求一个点右上方最优的点。

对于任意一个起点,我们第一段路只有两种可能,要么先向上再向右,要么先向右再向上。可以发现,答案和点到最优解之间的横/纵距离呈线性关系。

如图,\((i,j)\) 是 \((x,y)\) 这个点的最优解,则答案和 \(i-x\) 或 \(j-y\) 是有线性关系的。

那么我们可据此将每个点转换成直线,\(y_t=c_ix+f_{i,j}\),在李超树上求解最大值即可。

代码中我还没完全搞明白旋转之后的坐标关系,所以横纵坐标实际上是反过来的,其实这无关紧要,可以看作是关于 \(y=x\) 这根直线翻折得到,路径并没有受到改变,mxUmxR 统计的是路径上 \(c\) 的最大值。

#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int N = 6005, M = 3e6 + 5;
ll xl[N], xr[N], yl[N], yr[N], c[N], lshx[N], lshy[N];
int n, q, qx[M], qy[M];
ll ans[M], dp[N][N], mxR[N][N], mxU[N][N];
vector<int> inX[N], inY[N];

inline ll read() {
    ll x = 0;
    int f = 0;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) f |= c == '-';
    for (; isdigit(c); c = getchar()) x = x * 10 + (c & 15);
    return f ? -x : x;
}

namespace sgt {
const int L = 15005;
int tot, tag[L], lc[L], rc[L];
ll K[L], B[L];
int ln;
inline void add(ll k, ll b) { ++ln, K[ln] = k, B[ln] = b; }

inline ll y(int id, ll x) { return K[id] * x + B[id]; }

void Change(int& k, ll l, ll r, int id) {
    if (!k) k = ++tot;
    if (l == r) {
        if (!tag[k] || y(tag[k], l) < y(id, l)) tag[k] = id;
        return;
    }
    if (!tag[k]) return tag[k] = id, void();

    ll mid = l + r >> 1;
    ll y1 = y(tag[k], mid), y2 = y(id, mid);

    if (K[tag[k]] > K[id]) {
        if (y1 >= y2)
            Change(lc[k], l, mid, id);
        else {
            Change(rc[k], mid + 1, r, tag[k]);
            tag[k] = id;
        }
    } else if (K[tag[k]] < K[id]) {
        if (y1 >= y2)
            Change(rc[k], mid + 1, r, id);
        else {
            Change(lc[k], l, mid, tag[k]);
            tag[k] = id;
        }
    } else if (B[tag[k]] < B[id])
        tag[k] = id;
}
ll Query(int k, ll l, ll r, ll x) {
    if (!k) return -LONG_LONG_MAX;
    ll res = y(tag[k], x);
    ll mid = l + r >> 1;
    if (l == r) return res;
    if (x <= mid)
        return max(res, Query(lc[k], l, mid, x));
    else
        return max(res, Query(rc[k], mid + 1, r, x));
}
}  // namespace sgt
using namespace sgt;

// inline void chkmax(int& x, const int y) { x = x < y ? y : x; }
inline void chkmax(ll& x, const ll y) { x = x < y ? y : x; }

int main() {
    n = read(), q = read();
    for (int i = 1; i <= n; ++i) {
        ll t = read(), a = read(), b = read();
        c[i] = read() / 2;
        // before: x = t, y = a; x = t + abs(b - a), y = b;
        xl[i] = t + a;
        yl[i] = t - a;
        xr[i] = t + abs(b - a) + b;
        yr[i] = t + abs(b - a) - b;
        lshx[i * 2 - 1] = xl[i];
        lshx[i * 2] = xr[i];
        lshy[i * 2 - 1] = yl[i];
        lshy[i * 2] = yr[i];
    }

    sort(lshx + 1, lshx + n * 2 + 1);
    sort(lshy + 1, lshy + n * 2 + 1);
    int cntx = unique(lshx + 1, lshx + n * 2 + 1) - lshx - 1;
    int cnty = unique(lshy + 1, lshy + n * 2 + 1) - lshy - 1;

    for (int i = 1; i <= n; ++i) {
        xl[i] = lower_bound(lshx + 1, lshx + cntx + 1, xl[i]) - lshx;
        xr[i] = lower_bound(lshx + 1, lshx + cntx + 1, xr[i]) - lshx;
        yl[i] = lower_bound(lshy + 1, lshy + cnty + 1, yl[i]) - lshy;
        yr[i] = lower_bound(lshy + 1, lshy + cnty + 1, yr[i]) - lshy;
    }

    for (int i = 1; i <= n; ++i) {
        if (xl[i] == xr[i])
            for (int j = min(yl[i], yr[i]); j < max(yl[i], yr[i]); ++j)
                chkmax(mxU[xl[i]][j], c[i]);
        else  // yl[i] == yr[i]
            for (int j = min(xl[i], xr[i]); j < max(xl[i], xr[i]); ++j)
                chkmax(mxR[j][yl[i]], c[i]);
    }

    for (int i = cntx; i >= 1; --i) {
        for (int j = cnty; j >= 1; --j) {
            chkmax(dp[i][j], dp[i + 1][j] + 1ll * mxR[i][j] * (lshx[i + 1] - lshx[i]));
            chkmax(dp[i][j], dp[i][j + 1] + 1ll * mxU[i][j] * (lshy[j + 1] - lshy[j]));
        }
    }

    for (int i = 1; i <= q; ++i) {
        int p = read(), x = read();
        qx[i] = p + x, qy[i] = p - x;
        inX[lower_bound(lshx + 1, lshx + cntx + 1, qx[i]) - lshx].emplace_back(i);
    }

    for (int i = 1; i <= cntx; ++i) {
        for (int j = 1; j <= cnty; ++j) inY[j].clear();
        for (int j : inX[i])
            inY[lower_bound(lshy + 1, lshy + cnty + 1, qy[j]) - lshy].emplace_back(j);

        memset(lc, 0, sizeof(lc));
        memset(rc, 0, sizeof(rc));
        memset(tag, 0, sizeof(tag));
        tot = ln = 0;

        for (int j = cnty; j >= 1; --j) {
            add(mxR[i - 1][j], dp[i][j]);
            Change(lc[0], 0, 4e9, ln);

            for (int v : inY[j]) chkmax(ans[v], Query(1, 0, 4e9, lshx[i] - qx[v]));
        }
    }

    for (int i = 1; i <= cnty; ++i) inY[i].clear();

    for (int i = 1; i <= q; ++i)
        inY[lower_bound(lshy + 1, lshy + cnty + 1, qy[i]) - lshy].emplace_back(i);

    for (int i = 1; i <= cnty; ++i) {
        for (int j = 1; j <= cntx; ++j) inX[j].clear();
        for (int j : inY[i])
            inX[lower_bound(lshx + 1, lshx + cntx + 1, qx[j]) - lshx].emplace_back(j);

        memset(lc, 0, sizeof(lc));
        memset(rc, 0, sizeof(rc));
        memset(tag, 0, sizeof(tag));
        tot = ln = 0;

        for (int j = cntx; j >= 1; --j) {
            add(mxU[j][i - 1], dp[j][i]);
            Change(lc[0], 0, 4e9, ln);

            for (int v : inX[j]) chkmax(ans[v], Query(1, 0, 4e9, lshy[i] - qy[v]));
        }
    }

    for (int i = 1; i <= q; ++i) printf("%lld\n", ans[i]);

    return 0;
}

此题还有一种更精妙的 \(\mathcal O(nq)\) 做法,感兴趣的读者可以自行研究。Hint:用单调栈代替李超树。

标签:保镖,int,ll,Day3,JOISC,tag,2021,id,dp
From: https://www.cnblogs.com/misterrabbit/p/17450430.html

相关文章

  • 2021级《软件工程》测试河北宏志大学学生成绩管理系统
    2021级《软件工程》开发技能测试试卷(180分钟) 河北宏志大学学生成绩管理系统(卷面成绩40分) 河北宏志大学学生成绩管理系统1、项目需求:学生管理是各大院校的管理工作中尤为重视的一项工作,它一直以来是学校管理的一项重要的衡量指标。学生管理系统的应用解决了学校日常学生......
  • 202183300215 刘璎珂 实验六
    实验4#include<stdio.h>#include<stdlib.h>#include<string.h>#defineN100typedefstruct{charnum[10];//学号ints1;//期末成绩ints2;//平时成绩doublesum;//总评charlevel[......
  • 算法学习day37贪心part06-738、968
    packageLeetCode.greedypart06;/***738.单调递增的数字*当且仅当每个相邻位数上的数字x和y满足x<=y时,我们称这个整数是单调递增的。*给定一个整数n,返回小于或等于n的最大数字,且数字呈单调递增。*示例:*输入:n=332*输出:299**/public......
  • 算法学习day35贪心part04-860、406、452
    packageLeetCode.greedypart04;/***860.柠檬水找零*在柠檬水摊上,每一杯柠檬水的售价为5美元。顾客排队购买你的产品,(按账单bills支付的顺序)一次购买一杯。*每位顾客只买一杯柠檬水,然后向你付5美元、10美元或20美元。你必须给每个顾客正确找零,也就是说净交易是每......
  • 算法学习day36贪心part05-435、763、56
    packageLeetCode.greedypart05;importjava.util.Arrays;/***435.无重叠区间*给定一个区间的集合intervals,其中intervals[i]=[starti,endi]。返回需要移除区间的最小数量,使剩余区间互不重叠。*示例:*输入:intervals=[[1,2],[2,3],[3,4],[1,3]]*输出......
  • JOISC 2020 题解
    JOISC2020Day1建筑装饰4Building4我们发现\(A\)的个数是连续的,所以我们只需要DP出最大的\(A\)的个数和最大的\(B\)的个数,若两者都\(\gen\)那么就有解。然后再从后往前推出方案即可。https://qoj.ac/submission/109314*JOISC2020Day1汉堡肉HamburgSteak我们......
  • 算法学习day34贪心part03-1005、134、135
    packageLeetCode.greedypart03;/***1005.K次取反后最大化的数组和*给你一个整数数组nums和一个整数k,按以下方法修改该数组:*选择某个下标i并将nums[i]替换为-nums[i]。*重复这个过程恰好k次。可以多次选择同一个下标i。*以这种方式修改数组后,返回......
  • 算法学习day32贪心part02-122、55、45
    packageLeetCode.greedypart02;/***122.买卖股票的最佳时机II*给你一个整数数组prices,其中prices[i]表示某支股票第i天的价格。*在每一天,你可以决定是否购买和/或出售股票。*你在任何时候最多只能持有一股股票。你也可以先购买,然后在同一天出售。*返......
  • Nebula入门学习——day3 nGQL指南
    什么是nGQL¶nGQL(NebulaGraphQueryLanguage)是NebulaGraph使用的的声明式图查询语言,支持灵活高效的图模式,而且nGQL是为开发和运维人员设计的类SQL查询语言,易于学习。nGQL是一个进行中的项目,会持续发布新特性和优化,因此可能会出现语法和实际操作不一致的问题,如果遇到此......
  • 2021级《软件工程》 开发技能测试试卷(180分钟)源码
    开发工具:Eclipse前端技术:基础:html+css+JavaScript框架:JQuery+H-ui后端技术:Spring+SpringMVC+mybatis模板引擎:JSP数据库:mysql5.7.27jdk版本:1.8.0_251tomcat版本:Tomcat9.0数据库连接池:druidSpring-context.xml<?xmlversion="1.0"encoding="UTF-8"?><beansxmln......