【NOIP2015提高组】运输计划

https://daniu.luogu.org/problem/show?pid=2680

使完成所有运输计划的时间最短,也就是使时间最长的运输计划耗时最短。最大值最小问题考虑用二分答案,每次check(mid)检查时间最长的运输计划耗时是否小于等于mid,二分出使得check(mid)==true的最小mid值。

check函数怎么写是本题的难点。
耗时小于mid的运输计划不会影响check的结果。耗时大于mid的运输计划肯定需要改造他们的共同边才有可能使它们耗时都小于mid,而有多条共同边的时候肯定是改权值最大的更合算。如果改造了这条边可以使得原来时间最长的运输计划耗时也小于mid,则返回true,否则返回false。

所以读入数据时需要预处理下每个运输计划的耗时。

问题就在于怎么判断是否有公共边了。首先无根树转有根树,为了方便判断把边的权值放到子结点上。
可以用树剖+线段树把每一条路径上经过的所有点(除LCA)计数加一,然后看计数最大的点。但是更快更方便的方法是树上差分——对于每一条路径给两端的结点计数加1,给LCA计数减2。统计完之后做一遍树上前缀和,还可以在这个过程顺便求出计数最多的点。

注意这题卡常数非常厉害,记得用上快速读入、链式前向星、启发式合并+路径压缩的并查集。

#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
#define maxn 300005
using namespace std;
void scan(int &x)
{
    x = 0;
    char c;
    bool flag = false;
    while (!isdigit(c = getchar()))
    {
        if (c == ‘-‘)
            flag = true;
        if (c == EOF)
            return;
    }
    do
        x = x * 10 + c - ‘0‘;
    while (isdigit(c = getchar()));
    if (flag)
        x = -x;
}

int n, m;
struct edge
{
    int next, to, weight;
} edges[maxn * 2];
int head[maxn], ecnt = 1;
void add_edge(int u, int v, int w)
{
    edges[ecnt].to = v;
    edges[ecnt].weight = w;
    edges[ecnt].next = head[u];
    head[u] = ecnt++;

    edges[ecnt].to = u;
    edges[ecnt].weight = w;
    edges[ecnt].next = head[v];
    head[v] = ecnt++;
}
int weight[maxn], parent[maxn], length[maxn];
void build_tree(int v, int fr, int wei)
{
    parent[v] = fr;
    weight[v] = wei;
    length[v] = length[fr] + wei;
    for (int i = head[v]; i; i = edges[i].next)
    {
        if (edges[i].to != fr)
        {
            build_tree(edges[i].to, v, edges[i].weight);
        }
    }
}

namespace djs
{
int djs_parent[maxn];
void init()
{
    for (int i = 1; i <= n; i++)
        djs_parent[i] = -1;
}
int find(int x)
{
    if (djs_parent[x] < 0)
        return x;
    else
        return djs_parent[x] = find(djs_parent[x]);
}
void merge(int x, int y)
{
    x = find(x);
    y = find(y);
    djs_parent[y] = x;
}
}
int from[maxn], to[maxn], cost[maxn], lca[maxn];
vector<int> query_index[maxn];
bool visited[maxn];
void get_lca(int v)
{
    for (int i = head[v]; i; i = edges[i].next)
    {
        int w = edges[i].to;
        if (w != parent[v])
        {
            get_lca(w);
            djs::merge(v, w);
        }
    }
    visited[v] = true;

    for (int i = 0; i < query_index[v].size(); i++)
    {
        int q = query_index[v][i];
        int w = (from[q] == v) ? to[q] : from[q];
        if (visited[w])
            lca[q] = djs::find(w);
    }
}

int mark[maxn], maxmark;
void markdown(int i)
{
    mark[from[i]]++;
    mark[to[i]]++;
    mark[lca[i]] -= 2;
}
void push_down(int v)
{
    for (int i = head[v]; i; i = edges[i].next)
    {
        int w = edges[i].to;
        if (w != parent[v])
        {
            push_down(w);
            mark[v] += mark[w];
        }
    }
    if (mark[v] > mark[maxmark])
        maxmark = v;
    else if (mark[v] == mark[maxmark] && weight[v] > weight[maxmark])
        maxmark = v;
}
bool check(int k)
{
    for (int i = 1; i <= n; i++)
        mark[i] = 0;
    int maxcost = 0;
    int cnt = 0;
    for (int i = 1; i <= m; i++)
    {
        if (cost[i] > k)
        {
            cnt++;
            markdown(i);
            maxcost = max(maxcost, cost[i]);
        }
    }
    if (cnt == 0)
        return true;
    maxmark = 0;
    push_down(1);
    if (mark[maxmark] >= cnt && maxcost - weight[maxmark] <= k)
        return true;
    else
        return false;
}
int main()
{
    scan(n);
    scan(m);
    int a, b, c;
    for (int i = 1; i < n; i++)
    {
        scan(a);
        scan(b);
        scan(c);
        add_edge(a, b, c);
    }
    build_tree(1, 0, 0);
    for (int i = 1; i <= m; i++)
    {
        scan(from[i]);
        scan(to[i]);
        query_index[from[i]].push_back(i);
        query_index[to[i]].push_back(i);
    }
    djs::init();
    get_lca(1);
    for (int i = 1; i <= m; i++)
    {
        cost[i] = length[from[i]] + length[to[i]] - 2 * length[lca[i]];
    }

    int l = 0, r = 1000 * maxn, mid;
    while (l < r)
    {
        mid = (l + r) / 2;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    cout << l << endl;
    return 0;
}
时间: 2024-10-12 23:00:19

【NOIP2015提高组】运输计划的相关文章

[NOIP2015] 提高组 洛谷P2615 神奇的幻方

题目描述 幻方是一种很神奇的N*N矩阵:它由数字1,2,3,……,N*N构成,且每行.每列及两条对角线上的数字之和都相同. 当N为奇数时,我们可以通过以下方法构建一个幻方: 首先将1写在第一行的中间. 之后,按如下方式从小到大依次填写每个数K(K=2,3,…,N*N): 1.若(K−1)在第一行但不在最后一列,则将K填在最后一行,(K−1)所在列的右一列: 2.若(K−1)在最后一列但不在第一行,则将K填在第一列,(K−1)所在行的上一行: 3.若(K−1)在第一行最后一列,则将K填在(K−1)

洛谷-神奇的幻方-NOIP2015提高组复赛

题目描述 幻方是一种很神奇的N*N矩阵:它由数字1,2,3,……,N*N构成,且每行.每列及两条对角线上的数字之和都相同. 当N为奇数时,我们可以通过以下方法构建一个幻方: 首先将1写在第一行的中间. 之后,按如下方式从小到大依次填写每个数K(K=2,3,…,N*N): 1.若(K−1)在第一行但不在最后一列,则将K填在最后一行,(K−1)所在列的右一列: 2.若(K−1)在最后一列但不在第一行,则将K填在第一列,(K−1)所在行的上一行: 3.若(K−1)在第一行最后一列,则将K填在(K−1)

刷题总结——子串(NOIP2015提高组)

题目: 题目背景 NOIP2015 提高组 Day2 T2 题目描述 有两个仅包含小写英文字母的字符串 A 和 B .现在要从字符串 A 中取出 k 个互不重叠的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一个新的字符串,请问有多少种方案可以使得这个新串与字符串 B 相等?注意:子串取出的位置不同也认为是不同的方案. 输入格式 第一行是三个正整数 n,m,k,分别表示字符串 A 的长度,字符串 B 的长度,以及问题描述中所提到的 k ,每两个整数之间用一个空格隔

Noip2015 提高组 Day1

T1神奇的幻方 题目描述 幻方是一种很神奇的N*N矩阵:它由数字1,2,3,……,N*N构成,且每行.每列及两条对角线上的数字之和都相同. 当N为奇数时,我们可以通过以下方法构建一个幻方: 首先将1写在第一行的中间. 之后,按如下方式从小到大依次填写每个数K(K=2,3,…,N*N): 1.若(K−1)在第一行但不在最后一列,则将K填在最后一行,(K−1)所在列的右一列: 2.若(K−1)在最后一列但不在第一行,则将K填在第一列,(K−1)所在行的上一行: 3.若(K−1)在第一行最后一列,则将

[NOIP2015] 提高组 洛谷P2680 运输计划

题目背景 公元 2044 年,人类进入了宇宙纪元. 题目描述 L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球. 小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物 流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去.显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰. 为了鼓励科技创新,L 国国王同意小 P 的

[NOIP2015] 提高组 洛谷P2678 跳石头

题目背景 一年一度的“跳石头”比赛又要开始了! 题目描述 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块岩石作为比赛起点和终点.在起点和终点之间,有 N 块岩石(不含起点和终 点的岩石).在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达 终点. 为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳 跃距离尽可能长.由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能 移走起点和终点的岩石). 输入输出格式 输入格式

[NOIP2015] 提高组 洛谷P2679 子串

题目背景 无 题目描述 有两个仅包含小写英文字母的字符串 A 和 B.现在要从字符串 A 中取出 k 个互不重叠的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一 个新的字符串,请问有多少种方案可以使得这个新串与字符串 B 相等?注意:子串取出 的位置不同也认为是不同的方案. 输入输出格式 输入格式: 输入文件名为 substring.in. 第一行是三个正整数 n,m,k,分别表示字符串 A 的长度,字符串 B 的长度,以及问 题描述中所提到的 k,每两个整数之

[NOIP2015] 提高组 洛谷P2668 斗地主

题目描述 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的A到K加上大小王的共54张牌来进行的扑克牌游戏.在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响.每一局游戏中,一副手牌由n张牌组成.游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利. 现在,牛牛只想知道,对于自己的若干

洛谷 P2540 斗地主(NOIp2015提高组D1T3)

题目描述 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的A到K加上大小王的共54张牌来进行的扑克牌游戏.在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响.每一局游戏中,一副手牌由n张牌组成.游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利. 现在,牛牛只想知道,对于自己的若干