ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

省队集训Ⅱ-Day6

2021-06-06 14:05:25  阅读:179  来源: 互联网

标签:Ans Day6 Mn unsigned Sid Calc 集训 省队 Dis


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\n"); return 0;}
  for (;Mn > Total; Mn -= Total) ++Times;
  Ans = (double)(Total * (Total - Mn)) / ((Times - 1) * Total) + ((double)Mn / Times);
  printf("%.11lf\n", 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 \rfloor} + \frac{2(a - {x\%a})}{1 + \lfloor \frac xa \rfloor} + \frac{(k - x)\%b}{2 + \lfloor \frac {(k - x)}b \rfloor} + \frac{b - {(k - x)\%b}}{1 + \lfloor \frac {(k - x)}b \rfloor} \]

我们可以三分这个 \(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\n"); 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\n", Ans);
//    printf("A %u B %u\n", 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)\n", 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\n", 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\n"); 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\n", Ans);
  return 0;
}

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

标签:Ans,Day6,Mn,unsigned,Sid,Calc,集训,省队,Dis
来源: https://www.cnblogs.com/Wild-Donkey/p/14855164.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有