zoukankan      html  css  js  c++  java
  • 【HNOI 2017】影魔

    Problem

    Description

    影魔,奈文摩尔,据说有着一个诗人的灵魂。事实上,他吞噬的诗人灵魂早已成千上万。千百年来,他收集了各式各样的灵魂,包括诗人、牧师、帝王、乞丐、奴隶、罪人,当然,还有英雄。

    每一个灵魂,都有着自己的战斗力,而影魔,靠这些战斗力提升自己的攻击。

    奈文摩尔有 (n) 个灵魂,他们在影魔宽广的体内可以排成一排,从左至右标号 (1)(n)。第 (i) 个灵魂的战斗力为 (k_i),灵魂们以点对的形式为影魔提供攻击力,对于灵魂对 (i, j(i<j)) 来说,若不存在 (k_s(i<s<j)) 大于 (k_i) 或者 (k_j),则会为影魔提供 (p_1) 的攻击力(可理解为:当 (j = i + 1) 时,因为不存在满足 (i < s < j)(s),从而 (k_s) 不存在,这时提供 (p_1) 的攻击力;当 (j > i + 1) 时,若 (max{k_s | i < s < j}le min(k_i, k_j)),则提供 (p_1) 的攻击力);另一种情况,令 (c)(k_{i + 1}, k_{i + 2}, cdots, k_{j -1}) 的最大值,若 (c) 满足:(k_i < c < k_j),或者 (k_j < c < k_i),则会为影魔提供 (p_2) 的攻击力,当这样的 (c) 不存在时,自然不会提供这 (p_2) 的攻击力;其他情况的点对,均不会为影魔提供攻击力。

    影魔的挚友噬魂鬼在一天造访影魔体内时被这些灵魂吸引住了,他想知道,对于任意一段区间 ([a,b]),位于这些区间中的灵魂对会为影魔提供多少攻击力,即考虑所有满足 (ale i<jle b) 的灵魂对 (i, j) 提供的攻击力之和。

    顺带一提,灵魂的战斗力组成一个 (1)(n) 的排列:(k_1, k_1, cdots, k_n)

    Input Format

    第一行四个整数 (n,m,p_1,p_2)

    第二行 (n) 个整数数:(k_1, k_2,cdots, k_n)

    接下来 (m) 行,每行两个数 (a,b),表示询问区间 ([a,b]) 中的灵魂对会为影魔提供多少攻击力。

    Output Format

    共输出 (m) 行,每行一个答案,依次对应 (m) 个询问。

    Sample

    Input

    10 5 2 3
    7 9 5 1 3 10 6 8 2 4
    1 7
    1 9
    1 3
    5 9
    1 5
    

    Output

    30
    39
    4
    13
    16
    

    Range

    对于 (30\%) 的数据,(1le n, mle 500)

    另有 (30\%) 的数据,(p_1 = 2p_2)

    对于 (100\%) 的数据,(1le n,mle 200000, 1le p_1, p_2le 1000)

    Algorithm

    线段树

    Mentality

    蛮简单的一道题吧 (......)

    不难想到,我们应该首先求出两个数组 (ll[i],rr[i]) 分别代表 (i) 左边第一个比 (a[i]) 大的数的位置和右边第一个比 (a[i]) 大的数的位置。

    具体怎么求呢?维护一个单调递减的单调栈就好了。详见代码。

    然后我们发现,对于每个区间 ([i,i+1]) ,它们必定都有 (p1) 的贡献,这个其实在输入询问的时候就可以处理了,即 (ans+=(r-l)*p1)

    那么不难发现对于一个位置 (i) ,它对区间 ([ll[i]+1sim i-1,rr[i]]) 和区间 ([ll[i],i+1sim rr[i]-1]) 都能产生 (p2) 的贡献,那我们只需要将每个点 (i) 挂载在 (ll[i],rr[i]) 两个位置上,一旦扫到 (ll[i]) ,就将 ([i+1,rr[i]]) 这段区间都加上 (p2) ,扫到 (rr[i]) ,就将 (ll[i]) 位置加上 (p1) ,将 ([ll[i]+1,i-1]) 这段区间都加上 (p2) 即可统计区间贡献。

    那么如何统计答案呢?其实很简单,对于每个询问区间 ([l,r]) ,我们只需要保证统计的值全部都来自询问区间内的点对即可。那么换句话说,我们可以用总贡献减去不属于询问区间的贡献。

    即我们在扫到某个询问左端点前一个位置 (l-1) 时,记录下 ([l,r]) 的贡献和 (sum1) ,这些就是不属于询问区间的贡献,而扫到右端点 (r) 时记录下贡献和 (sum2) ,这些就是总贡献,那么答案就是 (sum2-sum1)

    很简单伐。

    注意事项:

    • 那些 ([i,i+1]) 的小贡献要加上。

    • 对于一个点 (i) ,若左边或右边没有更大的了,将贡献区间左右端点挂载 (0)(n+1) 上,因为这种情况并不能产生贡献。

    Code

    #include <cstdio>
    #include <iostream>
    #include <vector>
    using namespace std;
    #define ls (o << 1)
    #define rs ((o << 1) + 1)
    #define mid ((l + r) >> 1)
    int n, m, p1, p2, a[200001], ql[200001], qr[200001];
    int top, L, R, x, stack[200001], rr[200001], ll[200001];
    long long ans, sum[800005], adv[800005], sum1[200001], sum2[200001];
    vector<int> Mount[5][200005];
    void pushdown(int o, int l, int r) {
      adv[ls] += adv[o], adv[rs] += adv[o];
      sum[ls] += 1ll * (mid - l + 1) * adv[o];
      sum[rs] += 1ll * (r - mid) * adv[o];
      adv[o] = 0;
    }
    void add(int o, int l, int r) {
      if (L > R) return;
      if (l >= L && r <= R) {
        sum[o] += 1ll * (r - l + 1) * x, adv[o] += x;
        return;
      }
      pushdown(o, l, r);
      if (mid >= L) add(ls, l, mid);
      if (mid < R) add(rs, mid + 1, r);
      sum[o] = sum[ls] + sum[rs];
    }
    void query(int o, int l, int r) {
      if (l >= L && r <= R) {
        ans += sum[o];
        return;
      }
      pushdown(o, l, r);
      if (mid >= L) query(ls, l, mid);
      if (mid < R) query(rs, mid + 1, r);
      sum[o] = sum[ls] + sum[rs];
    }
    void Add(int l, int r, int w) {
      L = l, R = r, x = w;
      add(1, 0, n);
    }
    void Query(int l, int r) {
      L = l, R = r, ans = 0;
      query(1, 0, n);
    }
    int main() {
      freopen("3722.in", "r", stdin);
      freopen("3722.out", "w", stdout);
      cin >> n >> m >> p1 >> p2;
      for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
      stack[top = 0] = 0;  //初始值设为 0 和 n+1
      for (int i = 1; i <= n; i++) {
        while (top > 0 && a[stack[top]] < a[i]) top--;
        ll[i] = stack[top], stack[++top] = i;
      }  //单调栈预处理 ll,rr 数组
      stack[top = 0] = n + 1;
      for (int i = n; i >= 1; i--) {
        while (top > 0 && a[stack[top]] < a[i]) top--;
        rr[i] = stack[top], stack[++top] = i;
      }
      for (int i = 1; i <= n; i++) {
        Mount[1][rr[i]].push_back(i);
        Mount[2][ll[i]].push_back(i);
      }  //挂载贡献区间
      int l, r;
      for (int i = 1; i <= m; i++) {
        scanf("%d%d", &ql[i], &qr[i]);
        sum2[i] += 1ll * (qr[i] - ql[i]) * p1;
        Mount[3][ql[i] - 1].push_back(i);
        Mount[4][qr[i]].push_back(i);
      }  //挂载询问端点
      for (int i = 1; i <= n; i++) {
        for (int j = 0, limit = Mount[1][i].size(); j < limit; j++) {
          int to = Mount[1][i][j];
          Add(ll[to], ll[to], p1);
          Add(ll[to] + 1, to - 1, p2);
        }
        for (int j = 0, limit = Mount[2][i].size(); j < limit; j++) {
          int to = Mount[2][i][j];
          Add(to + 1, rr[to] - 1, p2);
        }
        for (int j = 0, limit = Mount[3][i].size(); j < limit; j++) {
          int to = Mount[3][i][j];
          Query(ql[to], qr[to]);
          sum1[to] = ans;
        }
        for (int j = 0, limit = Mount[4][i].size(); j < limit; j++) {
          int to = Mount[4][i][j];
          Query(ql[to], qr[to]);
          sum2[to] += ans;
        }
      }
      for (int i = 1; i <= m; i++) printf("%lld
    ", sum2[i] - sum1[i]);
    }
    
  • 相关阅读:
    php写的几种常见算法
    无状态登陆:JWT
    boostrap中日期控件使用
    boostrap中文件上传使用组件fileinput
    ubuntu环境下homestead安装运行nsq
    Ubuntu 下修改mysqlroot密码
    从git到lnmp代码发布
    maven的xml中报错:org.apache.maven.archiver.MavenArchiver.getManifest(org.apache.maven.project.MavenProject, org.apache.maven.archiver.MavenArchiveConfiguration)
    给mysql查询添加序号列
    maven jar包下载不下来
  • 原文地址:https://www.cnblogs.com/luoshuitianyi/p/10581251.html
Copyright © 2011-2022 走看看