• 考试总结(CE???)


    直接开写题解:

    (由于T1为暴力模拟,不进行整理)

    T2:

    扶苏给了你一棵树,这棵树上长满了幼嫩的新叶,我们约定这棵树的根是 1,每个节 点都代表树上的一个叶子。 如果你不知道什么叫树,你可以认为树是一个边数比节点个数少 1 的无向连通图。 我们如果约定节点 u 是树 T 的根,则可以定义一个节点 v 到根的路径为该无向图上 u, v 两个节点之间的简单路径上的节点集合(包括路径的两个端点)。可以证明,这样的简单路 径只有一条。 我们定义节点 x 是节点 y 的祖先(x 不等于 y),当且仅当 x 在 y 到根的路径上。 现在扶苏想在这棵树上选定一个集合,将其称之为幼嫩集合,来比较集合中的节点 哪个最幼嫩。注意到一旦集合中存在两个节点 u, v,使得 u 是 v 的祖先,那么一定 v 要比 u 更幼嫩,因为 v 是在 u 的枝丫上生长出来的,那么这样的集合就是没有意义的。也就是 说,扶苏所选择的集合一定满足要求“对于任意集合中的元素对 (u, v),u 不是 v 的祖 先”。 扶苏其实对这些节点哪个最幼嫩并不感兴趣,也对他能选出多少集合不感兴趣,因 为这些都是为了问你下面的问题而创造出的题目背景。 扶苏给每个节点都定义了一个权值,具体的,我们会给出一个参数 T,规定 i 号节点 的权值为 T i 。 我们定义一个幼嫩集合幼嫩指数为集合内节点的权值和。现在扶苏想请问你,对于 他所有可能选出的集合,这些集合的幼嫩指数之和是多少。 为了避免答案过大,请你输出答案对1000000007取模的结果。

    ZAY大佬分层讲了几种方法,分别可以拿到不同层级的分,

    1.直接输出答案,只得第一问的分

    2.暴搜,复杂度较高,但能拿到30分

    3.DP

    我们可以把这样的标签:

    变成这样的:

    考虑将每个节点的状态用子节点表示

    1.二叉树状态,每个节点仅有两个分支

    先考虑边界条件,即该节点是叶节点时,它没有子树甚至子节点,于是他自己的贡献为自身权值,

    再考虑有两个子节点的节点,其上之节点选择方案共有4种,分别是左节点,右节点,自己,左右两节点

    再由乘法原理易推知,当前节点的方案总数即为子树根节点方案总数加1之积,就是两子树互相搭配情况下,再算上某子树自身不算的方案总数,这也就是+1的用处

    而每个节点的分数就是某棵子树根节点分数乘另一棵子树方案总数,就是自己被选了多少次,由此,可推出答案

    2.非二叉树,

    每次将同一层的最左端的两棵子树进行如上操作,再将其看作一整体,与下一棵子树进行处理,直到所有子树处理完成

    按照以上DP思想处理就可以那全分辣!

    代码:

    #include <cstdio>
    
    typedef long long int ll;
    
    const int maxn = 1000005;
    const int MOD = 1000000007;
    
    template <typename T>
    inline void qr(T &x) {
      char ch;
      do { ch = getchar(); } while ((ch > '9') || (ch < '0'));
      do { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); } while ((ch >= '0') && (ch <= '9'));
    }
    
    int n, T;
    int MU[maxn], frog[maxn], gorf[maxn];
    bool vis[maxn];
    
    struct Edge {
      int v;
      Edge *nxt;
    
      Edge(const int _v, Edge *h) : v(_v), nxt(h) {}
    };
    Edge *hd[maxn];
    
    void dfs(const int u);
    
    int main() {
      freopen("dzy.in", "r", stdin);
      freopen("dzy.out", "w", stdout);
      qr(n); qr(T);
      if (T) {
        for (int i = 1; i <= n; ++i) {
          MU[i] = i;
        }
      } else {
        for (int i = 1; i <= n; ++i) {
          MU[i] = 1;
        }
      }
      for (int i = 1, u, v; i < n; ++i) {
        u = v = 0; qr(u); qr(v);
        hd[u] = new Edge(v, hd[u]);
        hd[v] = new Edge(u, hd[v]);
      }
      dfs(1);
      printf("%d
    ", frog[1] % MOD);
      return 0;
    }
    
    void dfs(const int u) {
      vis[u] = true;
      for (auto e = hd[u]; e; e = e->nxt) if (!vis[e->v]) {
        int v = e->v;
        dfs(v);
        frog[u] = (frog[u] * (gorf[v] + 1ll) % MOD) + (frog[v] * (gorf[u] + 1ll) % MOD);
        gorf[u] = (gorf[u] + gorf[v] + (1ll * gorf[u] * gorf[v])) % MOD;
      }
      frog[u] = (frog[u] + MU[u]) % MOD;
      ++gorf[u];
    }

    这么诡异的码风肯定是zay的啊

     T3:

    在一个兵荒马乱的年代,有一位画师叫浅溪,非常喜欢画锦鲤。战火烧到了泰 安,他的邻居都惊慌逃命,只有他不舍得池中锦鲤没有离开。这天晚上庭院失火, 池中的锦鲤化妖,用生命护住了画师的平安。

    扶苏被画师和锦鲤的故事深深地打动了。为了能让锦鲤和画师继续生活在一起,他 决定回到着火的庭院中灭掉大火。 画师的庭院可以抽象成一个有向图,每个点代表着一个着火的位置。为了量化火势 的大小,扶苏给每个点一个火力值,火力值越大,代表这个点的火势越强。风助火势, 火借风力,对于每一个着火点,都有可能因为大风使得火扩散到其他点。有向图的每条 边 <u,v> 代表大火是从点 u 扩散到点 v 的。需要注意的是一个点可能会扩散到很多 点,也可能是由很多点的大火一起扩散成的。为了不因为灭掉火源让画师发现有人在帮 他灭火,在任意时刻,扶苏不能灭掉任何一个不被任何点所扩散的点的火。一个点的火 被灭掉后,所代表该点的火扩散的所有边将消失。需要说明的是,虽然边消失了,但是 该点扩散到的所有点属性除入度以外都不会改变,更不会消失。 因为穿越的时间有限,扶苏只能灭掉最多 k 个点的火。忙着写题面的扶苏没有时间 算出他最多能扑灭多少火力值,于是他把这个问题交给了你。

    便于理解的题面版本: 给你一张有向图,每个点有一个点权。你可以任意选择一个有入度的点,获得它的 点权并把它和它的出边从图上删去。任意时刻不能选择没有入度的点。最多能选择 k 个 点,求最多能获得多少点权。   md我花了10分钟读题你告诉我有简化版???!!! 

    老规矩:

    1.输出样例 5分(我连输出都没打完就输出答案了,然而建树模板从T2粘下来的,一波窒息操作CE了)

    2.暴搜

    3.拓扑排序+贪心(有这个思路但时间不够了这题目背景竟该死的甜美

    4.也是拓扑,利用深搜原理和强连通分量性质对环进行缩点(tarjan),对于搜索树进行贪心,完成...

    我TM刚刚整了套什么?

    我们整个下午差不多听题解也是这么个状态:

    啊...就是这个这个...那个那个...讲的好啊!

    1 minite later

    (小声)你听懂了吗?

    代码奉上:

    #include <cstdio>
    #include <algorithm>
    #include <functional>
    #ifdef ONLINE_JUDGE
    #define freopen(a, b, c)
    #endif
    
    typedef long long int ll;
    
    namespace IPT {
      const int L = 1000000;
      char buf[L], *front=buf, *end=buf;
      char GetChar() {
        if (front == end) {
          end = buf + fread(front = buf, 1, L, stdin);
          if (front == end) return -1;
        }
        return *(front++);
      }
    }
    
    template <typename T>
    inline void qr(T &x) {
      char ch = IPT::GetChar(), lst = ' ';
      while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
      while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
      if (lst == '-') x = -x;
    }
    
    const int maxn = 1000006;
    
    struct Edge {
      int v;
      Edge *nxt;
    
      Edge(const int _v, Edge *h) : v(_v), nxt(h) {}
    };
    Edge *hd[maxn];
    
    int n, m, k, vistime, top, scnt;
    int MU[maxn], dfn[maxn], low[maxn], stack[maxn], belong[maxn], minv[maxn];
    bool instack[maxn], haveind[maxn];
    
    void tarjan(const int u);
    
    int main() {
      freopen("zay.in", "r", stdin);
      freopen("zay.out", "w", stdout);
      qr(n); qr(m); qr(k); MU[0] = 2333;
      for (int i = 1; i <= n; ++i) qr(MU[i]);
      for (int i = 1, u, v; i <= m; ++i) {
        u = v = 0; qr(u); qr(v);
        hd[u] = new Edge(v, hd[u]);
      }
      for (int i = 1; i <= n; ++i) if (!dfn[i]) {
        tarjan(i);
      }
      for (int u = 1; u <= n; ++u) {
        for (auto e = hd[u]; e; e = e->nxt) if (belong[u] != belong[e->v]) {
          haveind[belong[e->v]] = true;
        }
      }
      for (int i = 1; i <= scnt; ++i) if (!haveind[i]) {
        MU[minv[i]] = 0;
      }
      std::nth_element(MU + 1, MU + 1 + k, MU + 1 + n, std::greater<int>());
      int ans = 0;
      for (int i = 1; i <= k; ++i) {
        ans += MU[i];
      }
      printf("%d
    ", ans);
      return 0;
    }
    
    void tarjan(const int u) {
      dfn[u] = low[u] = ++vistime;
      instack[stack[++top] = u] = true;
      for (auto e = hd[u]; e; e = e->nxt) {
        int v = e->v;
        if (!dfn[v]) {
          tarjan(v);
          low[u] = std::min(low[u], low[v]);
        } else if (instack[v]) {
          low[u] = std::min(low[u], dfn[v]);
        }
      }
      if (dfn[u] == low[u]) {
        int v, &_mv = minv[++scnt];
        do {
          instack[v = stack[top--]] = false;
          belong[v] = scnt;
          if (MU[v] < MU[_mv]) _mv = v;
        } while (v != u);
      }
    }

    不打using namespace std的神仙...

    讲解内容放在这里,看培训完了能不能听懂...

    考虑DAG的情况放到普通有向图上会发生什么。

    有了子任务 3 的提示,我们可以考虑把整个图缩点,将其变成一个DAG来做。

    对于一个DAG,显然可以通过子任务 3 调整顺序的方式使得每个强连通分量的 选择情况除选点个数以外互不影响。故下面只讨论一个强连通分量内部的情况。

    一个强连通分量显然可以看作是一棵外向树加上很多边得到的。

    一棵外向树的定义:一个外向树的任意一个节点要么为叶节点,要么它与孩子 间的所有边都是由它指向孩子。

    一棵外向树显然是一个 DAG 。按照之前对 DAG 上情况的说明,显然我们可以 选择除了根节点以外的任意节点。

    因为一个强连通分量内部是互相连通的,于是我们不妨钦定一个点为根。

    对于一个没有入度的强连通分量,我们不妨钦定点权最小的点为根。这样显然选择的是最优的。

    对于一个有入度的强连通分量,我们不妨钦定那个有入度的点为根。这样在选择到只剩根节点的时候,因为根节点有入度,所以根节点是可以被选择的。于是这个强连通分量可以被全部选择。这显然是最优的。

    这样综合上述讨论,有入度的强连通分量可以随便选,没有入度的强连通分量 去掉最小的点权的点。剩下贪心取前 k 个就可以了。

    进行一次 tarjan的复杂度是 O(n+m),选前 k 个点可以排序一下。这样总复杂 度 O(m+nlogn),期望得分 35 分。注意到复杂度瓶颈在排序上,考虑我们只需要前 k 大而不需要具体前 k 个之间的大小关系,于是使用 std::nth_element()函数可以将复杂度降至 O(n+m)。期望得分 40 分。注意,输入规模到了 10 7 级别,需要 fread 来实现读入优化。

    nth_element()函数是一个理论复杂度为O(n)的排序函数(这么6?)

    //update 19/11/22(原谅我曾经英语不好)

    nth_element这个函数是找第n大的数,把第n大的数放在n的位置上,比起小的放在其左,反之置于其右,不保证有序,算是qsort的一部分,所以这只是用stl实现了qsort中的部分步骤,然后实现了需要排序才能进行查询的事情(其实某种意义上有种数据结构是能实现不排序查询nth大的数的...)

  • 相关阅读:
    一、HTML基础学习
    算法之求正整数的二进制
    JavaScript ajax返回状态
    ajax实现异步校验
    比较喜欢的一种求阶乘的方法:用递归求阶乘
    java中自定义异常类
    我对多态的理解
    包装类和基本类型区别?(integer和int取值范围一样大)
    JAVA中默认的编码方式
    抽象类和接口的区别
  • 原文地址:https://www.cnblogs.com/648-233/p/11067779.html
Copyright © 2020-2023  润新知