• 省队集训Ⅱ-Day6


    Day6

    由于这一天的 T1 过于值得整理, 一道题就干了我 (10000) 个字符的篇幅, 所以貌似今天只整了一道题, 但是这道题费了我一整天的时间, 还连累黄文鹤 00:30 都没睡. 这就是 Day6 了.

    City

    给四个点, (s_1), (s_2), (t_1), (t_2), 可以进行 (k) 次操作, 每次操作可以给一条边速度加 (1), 一个条速度为 (v) 的边的通过时间为 (frac 1v). 求进行操作后, 两个起点分别到两个终点的时间和最小值.

    因为操作在两条路径的公共边上时收益更大, 所以要求两条路径的交的长度.

    但是我也知道要这么做, 但是我不会求, 然后假设了一些好用的结论, 都被自己 Hack 了.

    但是我赌他的枪里没有子弹

    12:30 花了 (10min) 写了个 BFS 狗急跳墙, 只考虑路径没有交的情况, 能得多少分随缘.

    他的枪里果然没子弹

    我的错误算法得了 (85'), 我人都傻了. (可能是因为这是 ICPC 题, (95' = 0'), 所以没有出太多离谱的数据).

    unsigned n, m, Mn, A, B, Q[10005], Hd(0), Tl(0), Times(0), Total, S1, S2, T1, T2;
    double Ans1, Ans2, Ans;
    char flg(0);
    struct InEdge {
      unsigned From, To;
      inline const char operator<(const InEdge &x) const{
        return (this->From ^ x.From) ? (this->From < x.From) : (this->To < x.To);
      }
    }IE[5005];
    struct Edge;
    struct Node {
      Edge *Fst;
      unsigned Dis1, Dis2;
    }N[5005];
    struct Edge {
      Edge *Nxt;
      unsigned Used;
      Node *To;
    }E[10005], *CntE(E);
    inline void Link(Node *x, Node *y) {
      (++CntE)->Nxt = x->Fst, x->Fst = CntE;
      CntE->To = y;
      return;
    }
    int main() {
      n = RD(), m = RD(), Mn = RD();
      memset(N, 0x3f, sizeof(N));
      for (register unsigned i(1); i <= m; ++i)
        A = RD(), B = RD(), IE[i].From = min(A, B), IE[i].To = max(A, B);
      sort(IE + 1, IE + m + 1);
      for (register unsigned i(1); i <= m; ++i)
        if((IE[i].From ^ IE[i - 1].From) || (IE[i].To ^ IE[i - 1].To))
          Link(N + IE[i].From, N + IE[i].To), Link(N + IE[i].To, N + IE[i].From);
      S1 = RD(), T1 = RD(), S2 = RD(), T2 = RD();
      Q[++Tl] = S1, N[S1].Dis1 = 0, N[S2].Dis2 = 0;
      Node *now; Edge *Sid;
      while (Hd < Tl) {
        now = N + Q[++Hd];
        Sid = now->Fst;
        while (Sid < E + 20000) {
          if(Sid->To->Dis1 > 1000000000) {
            Sid->To->Dis1 = now->Dis1 + 1;
            Q[++Tl] = Sid->To - N;
          }
          Sid = Sid->Nxt;
        }
      }
      Hd = Tl = 0, Q[++Tl] = S2;
      while (Hd < Tl) {
        now = N + Q[++Hd], Sid = now->Fst;
        while (Sid < E + 20000) {
          if(Sid->To->Dis2 > 1000000000) 
            Sid->To->Dis2 = now->Dis2 + 1, Q[++Tl] = Sid->To - N;
          Sid = Sid->Nxt;
        }
      }
      Ans1 = N[T1].Dis1, Ans2 = N[T2].Dis2;
      Total = N[T1].Dis1 + N[T2].Dis2;
      Times = 2;
      if(!Total) {printf("0.00000000000
    "); return 0;}
      for (;Mn > Total; Mn -= Total) ++Times;
      Ans = (double)(Total * (Total - Mn)) / ((Times - 1) * Total) + ((double)Mn / Times);
      printf("%.11lf
    ", Ans);
      return 0;
    }
    

    正解(费这么大劲多拿个 (15') 真不如 (10min) 写个 BFS 来得爽快): 以每个点为起点, 跑 (O(n)) 的 BFS, 这样就能 (O(n^2)) 求全源最短路了. 容易知道, 两条路径的交一定是一段连续的路径, 因为如果分成多段, 从一段尾两条路径会分开, 到另一段开头合并, 这两段分开的一定可以走其中较短的一段, 不会使答案更劣. 所以只要枚举这一段公共路径的两个端点即可求出路径的交 (a) 和交对并的补 (b), 分别是被两条路径都经过的长度和两条路径单独经过的长度. 对于每个 (a) 值, 维护和它一起出现的最小的 (b) 值, 因为对同一个 (a) 来说, (b) 越小答案越优. 写出最后的时间和 (y) 对操作放到 (a) 的数量 (x) 的函数:

    值得一提的一个细节: 当枚举两个端点时, 会有情况是 (Dis_{s_1, i} + Dis_{t_1, j} < Dis_{s_1, j} + Dis_{t_1, i}) 并且 (Dis_{s_2, j} + Dis_{t_2, i} < Dis_{s_2, i} + Dis_{t_2, j}) 这是因为虽然路径交已经确定, 但是两条路径进入这段路径的方向没确定, 需要分类讨论每个点是从 (i) 端进, (j) 端出, 还是从 (j) 端进, (i) 端出.

    [y = frac{2(x\%a)}{2 + lfloor frac xa floor} + frac{2(a - {x\%a})}{1 + lfloor frac xa floor} + frac{(k - x)\%b}{2 + lfloor frac {(k - x)}b floor} + frac{b - {(k - x)\%b}}{1 + lfloor frac {(k - x)}b floor} ]

    我们可以三分这个 (x) 求出对于某个三元组 ((a, b, k)) 最小的 (y). 因为 (k) 不变, 每个 (a) 对应唯一的 (b), 所以这样的三元组一共有 (n) 个, 可以 (O(nlog_{frac{3}{2}}^k)) 处理得到答案. 最后的复杂度 (O(n^2 + nlog_{frac{3}{2}}k)).

    很显然这个题原来是 (3s), 但是场上要开 -O2, 所以改到了 (1s), 理论上不开 -O2 是过不了的, 千辛万苦调完了之后, 只得了 65', 剩下的都超时了, 开了 -O2 能过, 所以这就是正解了, 其中加了判重边的操作使程序更加鲁棒 (题目说了有重边).

    unsigned n, m, N2, Mn, A, B, Dis[5005][5005], Hd(0), Tl(0), Q[5005], Total, S1, S2, T1, T2, Minb[10005], TmpA(0), L, R, Mid1, Mid2;
    double Ans(1e9);
    char flg(0);
    struct InEdge {
      unsigned From, To;
      inline const char operator<(const InEdge &x) const{return (this->From ^ x.From) ? (this->From < x.From) : (this->To < x.To);}
    }IE[5005];
    struct Edge;
    struct Node {Edge *Fst; unsigned Dis1, Dis2;}N[5005], *now;
    struct Edge {Edge *Nxt; unsigned Used; Node *To;}E[10005], *CntE(E), *Sid;
    inline void Link(Node *x, Node *y) {(++CntE)->Nxt = x->Fst, x->Fst = CntE, CntE->To = y;}
    inline double Calc(unsigned x) {
      register unsigned Kx(Mn - x);
      register double Div1(x / B), Div2(Kx / A), Mod1(x % B), Mod2(Kx % A);
      return 2 * (Mod1 / (2 + Div1) + (B - Mod1) / (1 + Div1)) + Mod2 / (2 + Div2) + (A - Mod2) / (1 + Div2);
    }
    int main() {
      n = RD(), m = RD(), Mn = RD(), N2 = (n << 1); 
      memset(Dis, 0x3f, sizeof(Dis)), memset(Minb, 0x3f, sizeof(Minb));
      for (register unsigned i(1); i <= m; ++i)
        A = RD(), B = RD(), IE[i].From = min(A, B), IE[i].To = max(A, B);
      sort(IE + 1, IE + m + 1);
      for (register unsigned i(1); i <= m; ++i)
        if((IE[i].From ^ IE[i - 1].From) || (IE[i].To ^ IE[i - 1].To))
          Link(N + IE[i].From, N + IE[i].To), Link(N + IE[i].To, N + IE[i].From);
      S1 = RD(), T1 = RD(), S2 = RD(), T2 = RD();
      for (register unsigned i(1); i <= n; ++i) {
        Hd = Tl = 0, Q[++Tl] = i, Dis[i][i] = 0;
        while (Hd < Tl) {
          now = N + Q[++Hd], Sid = now->Fst;
          while (Sid) {
            if(Dis[i][Sid->To - N] > 1000000000)
              Dis[i][Sid->To - N] = Dis[i][now - N] + 1, Q[++Tl] = Sid->To - N;
            Sid = Sid->Nxt;
          }
        }
      }
      Minb[Dis[S1][T1] + Dis[S2][T2]] = 0;
      for (register unsigned i(1); i <= n; ++i) {
        for (register unsigned j(1); j <= n; ++j) {
          TmpA = min(Dis[S1][i] + Dis[T1][j], Dis[S1][j] + Dis[T1][i]) + min(Dis[S2][i] + Dis[T2][j], Dis[S2][j] + Dis[T2][i]);
          if(TmpA <= N2) {
            Minb[TmpA] = min(Minb[TmpA], Dis[i][j]);
          }
        }
      }
      if(!Minb[0]) {printf("0.00000000000
    "); return 0;}
      if(Minb[0] < 1000000000) Ans = 2 * ((double)(Mn % Minb[0]) / (2 + (Mn / Minb[0])) + (double)(Minb[0] - (Mn % Minb[0])) / (1 + (Mn / Minb[0])));
      for (register unsigned i(1); i <= N2; ++i) {
        if(Minb[i] > 1000000000) continue;
    //    printf("%.11lf
    ", Ans);
    //    printf("A %u B %u
    ", i, Minb[i]);
        L = 0, R = Mn, A = i, B = Minb[i];
        if(!B) {Ans = min(Ans, (double)(Mn % i) / (2 + (Mn / i)) + (double)(i - (Mn % i)) / (1 + (Mn / i))); continue;}
        while (L + 2 < R) {
          Mid1 = L + ((R - L + 2) / 3), Mid2 = R - ((R - L + 2) / 3);
    //      printf("[%u, %u] (%u, %u)
    ", L, R, Mid1, Mid2);
          if(Calc(Mid1) > Calc(Mid2)) {
            L = Mid1;
          } else {
            R = Mid2;
          }
        }
        Ans = min(min(Ans, Calc(L + 2)), min(Calc(L), Calc(L + 1)));
      }
      printf("%.11lf
    ", Ans);
      return 0;
    }
    

    但是我还是不满意, 作为历城二中最快的男人, 怎么能被卡常? 我把本地的 -O2 关掉, 删掉了好多使程序鲁棒的细节 (甚至不能保证快读的正确性), 只为过这 (20) 点, 将大部分的整数变成 unsigned short, 本来想用 float 搞浮点数, 但是又被卡精度了. 最后感谢刘少卿的 unsigned short & 的引用, 避免了数组寻址的重复计算. 最后将大部分比较操作改成位运算, 最终在没有 -O2 的情况下 AC. (期间想看看 std 的表现如何, 交了一发不开 -O2 的, 结果才得了 (50'), 还比不上我随手写的鲁棒程序, 我爆踩 Std). 接下来是这份非常让人满意的代码:

    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<set>
    #include<cstring>
    using namespace std;
    const unsigned short _0(0), _1(1), _2(2);
    const double __1(1), __2(2); 
    inline unsigned short RD() {
    	unsigned short InTmp(0); char InCh(getchar());
    	while (InCh < '0' || InCh > '9') {
        InCh = getchar();
      }
      while (InCh >= '0') {
        InTmp = (InTmp << 3) + (InTmp << 1) + InCh - '0';
        InCh = getchar();
      }
      return InTmp;
    }
    unsigned short n, m, Dis[5005][5005], Hd(0), Tl(0), Q[5005], S1, S2, T1, T2, Minb[5005], TmpA(0), Top(0x3f3f), now, CntE(0), Sid, Fst[5005];
    unsigned Mn, A, B;
    double Ans(10000.0);
    struct Edge {unsigned short To, Nxt;}E[10005];
    inline void Link(unsigned short x, unsigned short y) {E[++CntE].Nxt = Fst[x], Fst[x] = CntE, E[CntE].To = y;}
    inline double Calc(unsigned x) {
      register unsigned Kx(Mn - x);
      register double Div1(x / A), Div2(Kx / B), Mod1(x % A), Mod2(Kx % B);
      return __2 * (Mod1 / (_2 + Div1) + (A - Mod1) / (_1 + Div1)) + Mod2 / (_2 + Div2) + (B - Mod2) / (_1 + Div2);
    }
    signed main() {
      n = RD(), m = RD(), scanf("%u", &Mn);
      memset(Dis, 0x3f, sizeof(Dis)), memset(Minb, 0x3f, sizeof(Minb));
      for (register unsigned short i(1); i <= m; ++i)
        A = RD(), B = RD(), Link(A, B), Link(B, A);
      S1 = RD(), T1 = RD(), S2 = RD(), T2 = RD();
      for (register unsigned short i(1); i <= n; ++i) {
        Hd = Tl = _0, Q[++Tl] = i, Dis[i][i] = _0;
        while (Hd < Tl) {
          now = Q[++Hd], Sid = Fst[now];
          register unsigned short &Addin(Dis[i][now]);
          while (Sid) {
            if(Dis[i][E[Sid].To] & 0x2000)
              Dis[i][E[Sid].To] = Addin + _1, Q[++Tl] = E[Sid].To;
            Sid = E[Sid].Nxt;
          }
        }
      }
      Minb[0] = Dis[S1][T1] + Dis[S2][T2];
      for (register unsigned short i(1); i <= n; ++i) {
        register unsigned short &AddiS1(Dis[i][S1]), &AddiT1(Dis[i][T1]), &AddiS2(Dis[i][S2]), &AddiT2(Dis[i][T2]);
        for (register unsigned short j(1); j <= n; ++j) {
          register unsigned short &Addij(Dis[i][j]);
          if(Addij <= n) {
            TmpA = min(AddiS1 + Dis[T1][j], Dis[S1][j] + AddiT1) + min(AddiS2 + Dis[T2][j], Dis[S2][j] + AddiT2);
            Minb[Addij] = min(Minb[Addij], TmpA);
          }
        }
      }
      if(!Minb[0]) {printf("0.00000000000
    "); return 0;}
      if(Minb[_0] ^ 0x3f3f) Ans = (double)(Mn % Minb[_0]) / (_2 + (Mn / Minb[_0])) + (double)(Minb[_0] - (Mn % Minb[_0])) / (_1 + (Mn / Minb[_0]));
      for (register unsigned i(1); i <= m; ++i) {
        if(Minb[i] & 0x2000) continue;
        register unsigned L(0), R(Mn), Mid1, Mid2;
        A = i, B = Minb[i];
        if(B > Top) {continue;}
        Top = B;
        if(!B) {Ans = min(Ans, __2 * ((double)(Mn % i) / (_2 + (Mn / i)) + (double)(i - (Mn % i)) / (_1 + (Mn / i)))); continue;}
        if(Mn == 1) {Ans = min(Ans, Calc(1));continue;}
        while (L + 2 < R) {
          Mid1 = L + ((R - L + 2) / 3), Mid2 = R - ((R - L + 2) / 3);
          if(Calc(Mid1) > Calc(Mid2)) L = Mid1;
          else R = Mid2;
        }
        Ans = min(min(Ans, Calc(L + _2)), min(Calc(L), Calc(L + _1)));
      }
      printf("%.11lf
    ", Ans);
      return 0;
    }
    

    感谢黄文鹤提供的 C++ 版的 Special Judge.

  • 相关阅读:
    js push(),pop(),shift(),unshift()
    bootstrap fileinput 上传文件
    关于dataTable 生成JSON 树
    postgresql+ C#+ DHTMLX 学习汇总
    java_时间戳与Date_相互转化的实现代码
    SparkML之推荐算法ALS
    ALS部署Spark集群入坑记
    test
    迁移数据库mysql
    JVM运行机制
  • 原文地址:https://www.cnblogs.com/Wild-Donkey/p/14855164.html
Copyright © 2020-2023  润新知