省队集训Ⅱ-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\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.

上一篇:动手写CPU----day6验证逻辑和移位指令


下一篇:day6-加密md5