1. 程式人生 > 其它 >省選日記 Day11 - Day15

省選日記 Day11 - Day15

省選日記 Day \(11\) - Day \(15\)

Day \(11\) Apr 14, 2022, Thursday

SDOI2019 染色

一開始一眼看出 \(O(n^3)\) 的做法, 設 \(f_{i, j, k}\) 表示計算到第 \(i\) 列, \((i, 1)\) 為顏色 \(j\), \((i, 2)\) 為顏色 \(k\) 的方案數. 統計 \(U_{i, j}\) 作為所有 \(f_{i, j, x}\) 的總和, 統計 \(D_{i, j}\) 作為所有 \(f_{i, x, j}\) 的總和, \(Sum_i\) 作為所有 \(f\), 轉移是:

\[f_{i, j, k} = Sum_{i - 1} - U_{i - 1, j} - D_{i - 1, k} + f_{i - 1, j, k} \]

然後把和已經確定的位置的顏色相悖的 \(f\)

值賦為 \(0\). 我一直認為這個題需要什麼奇妙科技以至於想了一個小時沒有進展, 看了兩眼題解發現貌似用不到什麼奇技淫巧, 也是從這個暴力 DP 上發展而來的, 於是繼續之前的 DP 進行優化.

如果滾動陣列, 可以寫成:

\[f_{i, j} += Sum - U_i - D_j \]

發現這個東西可以轉化為全域性加法, 行減, 列減, 全域性求和, 行求和, 列求和, 只保留某行或某列的歸零, 只保留單點的歸零, 對角線刪除. 行減列減容易維護, 只需要每行每列記錄增加數量總和 \(PR_i\), \(PC_i\), 全域性操作也是一樣, 只需要記錄全域性每個元素增加的數量總和 \(Pl\)

. 對於單點修改, 我們記錄一個矩陣 \(f_{i, j}\) 表示每個點增加的數量總和, 維護 \(Sum\) 表示矩陣 \(f\) 的總和, \(U_i\)\(D_i\) 仍然表示矩陣第 \(i\) 行和第 \(i\) 列的總和.

全域性總和是 \(Sum + c(c - 1)Pl + (c - 1)\sum_{i = 1}^n (PR_i + PC_i)\), 第 \(i\) 行的總和是 \(U_i + (c - 1)(Pl + PR_i) + \sum_{j = 1, j \neq i}^n PC_j\), 第 \(i\) 列的總和是 \(D_i + (c - 1)(Pl + PC_i) + \sum_{j = 1, j \neq i}^n PR_j\)

.

最後是歸零, 我們求一個單點的值的時間是 \(O(1)\) 的, 所以可以在 \(O(c)\) 之內求出所有保留位置的值, 直接賦到 \(f\) 裡面, 清空所有的 \(PR\), \(PC\), \(Pl\) 值. 我們發現, \(f\) 最多同時只有一行或一列或一個點有值, 所以不如直接存行的總和和列的總和來代表 \(f\).

直接做可以搞到 \(96'\) 的好成績.

const unsigned long long Mod(1000000009);
inline void Mn(unsigned long long& x) {x -= ((x >= Mod) ? Mod : 0);}
inline void Mn(unsigned& x) {x -= ((x >= Mod) ? Mod : 0);}
unsigned a[100005][2];
unsigned long long mm;
unsigned m, n;
unsigned A, B, C, D, t;
unsigned Cnt(0), Ans(0);
struct Sta {
  unsigned long long Pl, SumPR, SumPC, SumExist;
  unsigned PR[10005], PC[10005], Exist[10005], Pos;
  char Type;
  inline unsigned long long Sum () {
    return (SumExist + mm * Pl + (SumPR + SumPC) * (m - 1)) % Mod;
  }
  inline unsigned long long Get(unsigned x, unsigned y) {
    unsigned Ex(0);
    if(Type == 1) Ex = ((x == Pos) ? Exist[y] : 0);
    if(Type == 2) Ex = ((y == Pos) ? Exist[x] : 0);
    return (Pl + PR[x] + PC[y] + Ex) % Mod;
  }
  inline unsigned long long GetRow(unsigned x) {
    unsigned Ex(0);
    if(Type == 1) Ex = ((x == Pos) ? (Mod + SumExist - Exist[x]) : 0);
    if(Type == 2) Ex = ((x == Pos) ? 0 : Exist[x]);
    return ((Pl + PR[x]) * (m - 1) + Mod - PC[x] + SumPC + Ex) % Mod;
  }
  inline unsigned long long GetColumn(unsigned x) {
    unsigned Ex(0);
    if(Type == 1) Ex = ((x == Pos) ? 0 : Exist[x]);
    if(Type == 2) Ex = ((x == Pos) ? (Mod + SumExist - Exist[x]) : 0);
    return ((Pl + PC[x]) * (m - 1) + Mod - PR[x] + SumPR + Ex) % Mod;
  }
  inline void Clr() {
    Pl = 0;
    memset(PR + 1, 0, m << 2);
    memset(PC + 1, 0, m << 2);
  }
  inline void Udt() {
    SumExist = SumPR = SumPC = 0;
    for (unsigned i(1); i <= m; ++i) SumExist += Exist[i];
    for (unsigned i(1); i <= m; ++i) SumPC += PC[i];
    for (unsigned i(1); i <= m; ++i) SumPR += PR[i];
    SumExist %= Mod;
    SumPC %= Mod;
    SumPR %= Mod;
  }
}cl[2], *Cur(cl + 0), *Lst(cl + 1);
signed main() {
  n = RD(), mm = (m = RD()) - 1, mm = mm * m % Mod;
  for (unsigned i(1); i <= n; ++i) a[i][0] = RD();
  for (unsigned i(1); i <= n; ++i) a[i][1] = RD();
  if(a[1][0]) {
    Cur->Type = 1, Cur->Pos = a[1][0];
    if(a[1][1]) Cur->Exist[a[1][1]] = 1, Cur->SumExist = 1;
    else Cur->PR[Cur->Pos] = 1, Cur->SumPR = 1;
  }
  else {
    if(!a[1][1]) Cur->Type = 0, Cur->Pl = 1; 
    else Cur->Type = 2, Cur->PC[Cur->Pos = a[1][1]] = 1, Cur->SumPC = 1;
  }
  for (unsigned i(2); i <= n; ++i) {
    swap(Lst, Cur), Cur->Pl = Lst->Pl + Lst->Sum(), Mn(Cur->Pl); 
    for (unsigned j(1); j <= m; ++j) Cur->PR[j] = Lst->PR[j] + Mod - Lst->GetRow(j), Mn(Cur->PR[j]);
    for (unsigned j(1); j <= m; ++j) Cur->PC[j] = Lst->PC[j] + Mod - Lst->GetColumn(j), Mn(Cur->PC[j]);
    memcpy(Cur->Exist + 1, Lst->Exist + 1, m << 2), Cur->Type = Lst->Type, Cur->Pos = Lst->Pos;
    if(a[i][0]) {
      if (a[i][1]) {
        unsigned TmpC(Cur->Get(a[i][0], a[i][1]));
        memset(Cur->Exist + 1, 0, m << 2), Cur->Exist[a[i][1]] = TmpC;
      } else {
        for (unsigned j(1); j <= m; ++j) Lst->Exist[j] = Cur->Get(a[i][0], j); Lst->Exist[a[i][0]] = 0;
        memcpy(Cur->Exist + 1, Lst->Exist + 1, m << 2);
      }
      Cur->Clr(), Cur->Type = 1, Cur->Pos = a[i][0];
    } else {
      if (a[i][1]) {
        for (unsigned j(1); j <= m; ++j) Lst->Exist[j] = Cur->Get(j, a[i][1]); Lst->Exist[a[i][1]] = 0; 
        memcpy(Cur->Exist + 1, Lst->Exist + 1, m << 2);
        Cur->Clr(), Cur->Type = 2, Cur->Pos = a[i][1];
      }
    }
    Cur->Udt();
  }
  printf("%llu\n", Cur->Sum());
  return Wild_Donkey;
}

發現有兩個 Type, 也就是說 \(Exist\) 可能是行, 也可能是列, 我們使 \(Exist\) 只表示某一行的資訊. 這樣相當於不存在某一列第一行是 \(0\), 而第二行非零的情況, 如果出現, 轉置所維護的矩陣並且上下翻轉輸入序列. 仍然是 \(96'\).

const unsigned long long Mod(1000000009);
inline void Mn(unsigned long long& x) {x -= ((x >= Mod) ? Mod : 0);}
inline void Mn(unsigned& x) {x -= ((x >= Mod) ? Mod : 0);}
unsigned a[100005][2];
unsigned long long mm;
unsigned m, n;
unsigned A, B, C, D, t;
unsigned Cnt(0), Ans(0);
char Type, EType;
struct Sta {
  unsigned long long SumPR, SumPC, SumExist;
  unsigned PR[10005], PC[10005], Exist[10005], Pos;
  inline unsigned long long Sum () {
    return (SumExist + (SumPR + SumPC) * (m - 1)) % Mod;
  }
  inline unsigned long long GetRow(unsigned x) {
    unsigned Ex(0);
    if(EType ^ Type) Ex = Exist[x];
    else Ex = ((x == Pos) ? SumExist : 0);
    return ((unsigned long long)PR[x] * (m - 1) + Mod - PC[x] + SumPC + Ex) % Mod;
  }
  inline unsigned long long GetColumn(unsigned x) {
    unsigned Ex(0);
    if(EType ^ Type) Ex = ((x == Pos) ? SumExist : 0);
    else Ex = Exist[x];
    return ((unsigned long long)PC[x] * (m - 1) + Mod - PR[x] + SumPR + Ex) % Mod;
  }
  inline unsigned long long Get(unsigned x, unsigned y) {
    if(x == y) return 0;
    unsigned Ex;
    if(EType ^ Type) Ex = ((y == Pos) ? Exist[x] : 0);
    else Ex = ((x == Pos) ? Exist[y] : 0);
    return (PR[x] + PC[y] + Ex) % Mod;
  }
  inline void Clr() {
    memset(PR + 1, 0, m << 2), memset(PC + 1, 0, m << 2), SumPR = SumPC = 0; 
  }
}cl[2], *Cur(cl + 0), *Lst(cl + 1);
inline void Flip() {
  Type ^= 1;
  swap(Cur->SumPR, Cur->SumPC);
  for (unsigned i(1); i <= m; ++i) swap(Cur->PC[i], Cur->PR[i]);
}
signed main() {
  n = RD(), mm = (m = RD()) - 1, mm = mm * m % Mod;
  for (unsigned i(1); i <= n; ++i) a[i][0] = RD();
  for (unsigned i(1); i <= n; ++i) a[i][1] = RD();
  if(a[1][1]) EType = Type = 1, swap(a[1][1], a[1][0]);
  if(a[1][0]) {
    Cur->Pos = a[1][0];
    if(a[1][1]) Cur->Exist[a[1][1]] = 1, Cur->SumExist = 1;
    else Cur->PR[Cur->Pos] = 1, Cur->SumPR = 1;
  } else {for (unsigned i(1); i <= m; ++i) Cur->PR[i] = 1; Cur->SumPR = m;}
  for (unsigned i(2); i <= n; ++i) {
    swap(Lst, Cur), Cur->SumPR = Cur->SumPC = 0;
    unsigned long long Pl(Lst->Sum());
    for (unsigned j(1); j <= m; ++j) 
      Cur->PR[j] = Lst->PR[j] + Mod - Lst->GetRow(j), Mn(Cur->PR[j]), Mn(Cur->PR[j] += Pl), Mn(Cur->SumPR += Cur->PR[j]);
    for (unsigned j(1); j <= m; ++j) 
      Cur->PC[j] = Lst->PC[j] + Mod - Lst->GetColumn(j), Mn(Cur->PC[j]), Mn(Cur->SumPC += Cur->PC[j]);
    memcpy(Cur->Exist + 1, Lst->Exist + 1, m << 2), Cur->Pos = Lst->Pos, Cur->SumExist = Lst->SumExist; 
    if(a[i][1]) {swap(a[i][1], a[i][0]); if(!Type) Flip(); }
    else if(Type) Flip();
    if(a[i][0]) {
      if (a[i][1]) {
        unsigned TmpC(Cur->Get(a[i][0], a[i][1]));
        memset(Cur->Exist + 1, 0, m << 2), Cur->SumExist = Cur->Exist[a[i][1]] = TmpC;
      } else {
        Cur->SumExist = 0;
        for (unsigned j(1); j <= m; ++j) Mn(Cur->SumExist += (Lst->Exist[j] = Cur->Get(a[i][0], j)));
        memcpy(Cur->Exist + 1, Lst->Exist + 1, m << 2);
      }
      Cur->Clr(), Cur->Pos = a[i][0], EType = Type;
    }
  }
  printf("%llu\n", Cur->Sum());
  return Wild_Donkey;
}

Day \(12\) Apr 15, 2022, Friday

還是那道題

看了題解發現我超脫了題解的路數, 以至於題解的方法無法優化這個做法, 而我也完全沒有辦法優化這個做法. 重新審視我們的轉移, 發現每個階段的轉移只和當前列的狀態有關, 而當前列為空白時, 所有狀態從上一個階段到下一個階段的轉移是相同的. 而當前列非空白時, 這個階段有效狀態數最多是 \(O(c)\) 的.

仍然是遇到第二行有確定顏色就交換兩行, 我們用 \(f_{i, j}\) 表示第 \(i\) 個非空列, 第二行是顏色 \(j\) 的方案數. 處理 \(g_{i, 0/1/2/3/4}\) 用來轉移, 表示相鄰兩個非空列中間隔了 \(i\) 列, 兩端狀態為 \(0/1/2/3/4\) 五種情況的轉移係數. 兩端共四個格子, 這五種情況分別是:

  • \(0\) 表示四個顏色各不相同
  • \(1\) 表示同行的顏色相同而同列的不同
  • \(2\) 表示對角線上顏色相同而同行或同列顏色不同
  • \(3\) 表示只有一行顏色相同, 其餘兩個格子為不同顏色
  • \(4\) 表示只有一個對角線顏色相同, 其餘兩個格子為不同顏色.

發現有情況無法轉移, 就是兩端列存在空白列的情況, 這時需要預處理第一個階段的 DP 值, 並且通過最後一個階段的 DP 值算出答案.

\(g\) 陣列線性遞推即可求出.

const unsigned long long Mod(1000000009);
inline void Mn(unsigned long long& x) {x -= ((x >= Mod) ? Mod : 0);}
inline void Mn(unsigned& x) {x -= ((x >= Mod) ? Mod : 0);}
bitset<100005> Flp;
unsigned a[100005][2], Pos[100005], f[100005], g[100005][5];
unsigned long long mm, M1, M2, M3, M4, Sum;
unsigned m, n;
unsigned Cnt(0), Ans(0);
inline unsigned ColorTwo(unsigned x) {return a[x][Flp[x] ^ 1];}
inline unsigned ColorOne(unsigned x) {return a[x][Flp[x]];}
inline unsigned long long Onesided(unsigned x) {
  if(!x) return 1; --x;
  return ((g[x][0] * M2 % Mod) * M3 + g[x][1] + g[x][2] + ((g[x][3] + g[x][4]) * M2 << 1)) % Mod;
}
inline void Mul(unsigned long long x) {
  Sum = Sum * x % Mod;
  for (unsigned i(1); i <= m; ++i) f[i] = f[i] * x % Mod;
}
inline void Add(unsigned x) {
  Sum = (Sum + (unsigned long long)m * x) % Mod;
  for (unsigned i(1); i <= m; ++i) Mn(f[i] += x);
}
inline void Def(unsigned x) {
  Sum = (m * x) % Mod;
  for (unsigned i(1); i <= m; ++i) f[i] = x;
}
inline void Set(unsigned x, unsigned y) {
  Mn(Sum += y + Mod - f[x]), Mn(Sum), f[x] = y;
}
inline unsigned long long Find(unsigned x) {
  return f[x];
}
signed main() {
  n = RD(), mm = (m = RD()) - 1, mm = mm * m % Mod;
  M1 = m - 1, M2 = m - 2, M3 = m - 3, M4 = m - 4; 
  for (unsigned i(1); i <= n; ++i) a[i][0] = RD();
  for (unsigned i(1); i <= n; ++i) a[i][1] = RD();
  g[0][0] = g[0][2] = g[0][4] = 1, g[0][1] = g[0][3] = 0;
  for (unsigned i(1); i <= n; ++i) {
    g[i][0] = (g[i - 1][0] * ((M3 * M4 + 1) % Mod) + g[i - 1][1] + g[i - 1][2] + ((g[i - 1][3] + g[i - 1][4]) * M3 << 1) % Mod) % Mod;
    g[i][1] = ((g[i - 1][0] * M2 % Mod) * M3 + g[i - 1][2] + (g[i - 1][4] * M2 << 1)) % Mod;
    g[i][2] = ((g[i - 1][0] * M2 % Mod) * M3 + g[i - 1][1] + (g[i - 1][3] * M2 << 1)) % Mod;
    g[i][3] = ((g[i - 1][0] * M3 % Mod) * M3 + g[i - 1][2] + g[i - 1][3] * M2 + g[i - 1][4] * (M3 + M2)) % Mod;
    g[i][4] = ((g[i - 1][0] * M3 % Mod) * M3 + g[i - 1][1] + g[i - 1][4] * M2 + g[i - 1][3] * (M3 + M2)) % Mod;
  }
  for (unsigned i(1); i <= n; ++i) if(a[i][0] | a[i][1]) {Pos[++Cnt] = i; if(a[i][1]) Flp[Cnt] = 1;}
  for (unsigned i(1); i <= Cnt; ++i) a[i][0] = a[Pos[i]][0], a[i][1] = a[Pos[i]][1];
  if(!Cnt) {
    printf("%llu\n", (Onesided(n - 1) * m % Mod) * M1 % Mod);
    return 0;
  }
  unsigned CT(ColorTwo(1)), Val(Onesided(Pos[1] - 1));
  if(CT) Sum = f[CT] = Val;
  else {for (unsigned i(1); i <= m; ++i) f[i] = Val; f[ColorOne(1)] = 0, Sum = Val * M1 % Mod;}
  for (unsigned i(2); i <= Cnt; ++i) {
    if(0) {
      CT = ColorTwo(Cnt), Ans = 0;
      for (unsigned i(1); i <= m; ++i) if(CT ^ i) Mn(Ans += f[i]);
      printf("%llu\n", Ans);
    }
    unsigned A(a[i - 1][0]), B(a[i - 1][1]), C(a[i][0]), D(a[i][1]), *Len(g[Pos[i] - Pos[i - 1] - 1]);
    if(Flp[i]) swap(A, B), swap(C, D), Flp[i - 1] = (Flp[i - 1] ^ 1);
    if(Flp[i - 1]) {
      if(B == C) {
        unsigned long long PTmp(Sum * Len[4] % Mod);
        Mul(Mod + Len[2] - Len[4]), Add(PTmp), Set(ColorOne(i), 0);
      } else {
        unsigned long long FC(Find(C)), PTmp(((Mod + Sum - FC) * Len[0] + FC * Len[3]) % Mod);
        unsigned long long AB((FC * Len[1] + (Mod + Sum - FC) * Len[3]) % Mod);
        Mul(Mod + Len[4] - Len[0]), Add(PTmp), Set(C, 0), Set(B, AB);
      }
    } else {
      if(A == C) {
        unsigned long long PTmp(Sum * Len[3] % Mod);
        Mul(Mod + Len[1] - Len[3]), Add(PTmp), Set(ColorOne(i), 0);
      } else {
        unsigned long long FC(Find(C)), PTmp(((Mod + Sum - FC) * Len[0] + FC * Len[4]) % Mod);
        unsigned long long AB((FC * Len[2] + (Mod + Sum - FC) * Len[4]) % Mod);
        Mul(Mod + Len[3] - Len[0]), Add(PTmp), Set(C, 0), Set(A, AB);
      }
    }
    if(D) {
      unsigned TmpF(Find(D));
      Def(0), Set(D, TmpF);
    }
  }
  CT = ColorTwo(Cnt), Ans = 0;
  for (unsigned i(1); i <= m; ++i) if(CT ^ i) Mn(Ans += f[i]);
  printf("%llu\n", Ans * Onesided(n - Pos[Cnt]) % Mod);
  return Wild_Donkey;
}

發現瓶頸是對於每個階段的 \(f\) 陣列進行全域性修改查詢和單點修改和查詢, 由於模數較大, 所以無法線性, 但是可以通過線段樹做到 \(O(n\log c + c)\). 把快速查詢的部分分程式碼複製過來, 刪除全域性賦值即可拿到 \(100'\) 的好成績.

const unsigned long long Mod(1000000009);
inline void Mn(unsigned long long& x) {x -= ((x >= Mod) ? Mod : 0);}
inline void Mn(unsigned& x) {x -= ((x >= Mod) ? Mod : 0);}
bitset<100005> Flp;
unsigned a[100005][2], Pos[100005], g[100005][5];
unsigned long long mm, M1, M2, M3, M4;
unsigned OpV, OpP;
unsigned m, n;
unsigned Cnt(0), Ans(0);
inline unsigned ColorTwo(unsigned x) {return a[x][Flp[x] ^ 1];}
inline unsigned ColorOne(unsigned x) {return a[x][Flp[x]];}
struct Node {
  Node *LS, *RS;
  unsigned Sum, Mul, Pls;
  inline void Build(unsigned L, unsigned R);
  inline void PsDw(unsigned long long Llen, unsigned long long Rlen) {
    if(Mul ^ 1) {
      LS->Mul = (unsigned long long)Mul * LS->Mul % Mod;
      LS->Pls = (unsigned long long)Mul * LS->Pls % Mod;
      LS->Sum = (unsigned long long)Mul * LS->Sum % Mod;
      RS->Mul = (unsigned long long)Mul * RS->Mul % Mod;
      RS->Pls = (unsigned long long)Mul * RS->Pls % Mod;
      RS->Sum = (unsigned long long)Mul * RS->Sum % Mod;
      Mul = 1;
    }
    if(Pls) {
      Mn(LS->Pls += Pls), Mn(RS->Pls += Pls);
      LS->Sum = (LS->Sum + Pls * Llen) % Mod;
      RS->Sum = (RS->Sum + Pls * Rlen) % Mod;
      Pls = 0;
    }
  }
  inline void Define(unsigned L, unsigned R) {
    if(L == R) {Mul = 1, Pls = 0, Sum = OpV; return;}
    unsigned Mid((L + R) >> 1);
    this->PsDw(Mid - L + 1, R - Mid);
    if(OpP <= Mid) LS->Define(L, Mid);
    else RS->Define(Mid + 1, R);
    Sum = LS->Sum + RS->Sum, Mn(Sum);
  }
  inline void Qry(unsigned L, unsigned R) {
    if(L == R) {OpV = Sum; return;}
    unsigned Mid((L + R) >> 1);
    this->PsDw(Mid - L + 1, R - Mid);
    if(OpP <= Mid) LS->Qry(L, Mid);
    else RS->Qry(Mid + 1, R);
  }
}N[200005], *CntN(N);
inline void Node::Build(unsigned L, unsigned R) {
  Mul = 1, Pls = 0, Sum = 0;
  if(L == R) return;
  unsigned Mid((L + R) >> 1);
  (LS = ++CntN)->Build(L, Mid);
  (RS = ++CntN)->Build(Mid + 1, R);
}
inline unsigned long long Onesided(unsigned x) {
  if(!x) return 1; --x;
  return ((g[x][0] * M2 % Mod) * M3 + g[x][1] + g[x][2] + ((g[x][3] + g[x][4]) * M2 << 1)) % Mod;
}
inline void Multiple(unsigned long long x) {
  N->Mul = N->Mul * x % Mod;
  N->Pls = N->Pls * x % Mod;
  N->Sum = N->Sum * x % Mod;
}
inline void Add(unsigned long long x) {
  Mn(N->Pls += x);
  N->Sum = (N->Sum + m * x) % Mod;
}
inline void Set(unsigned x, unsigned y) {
  if(!x) return;
  OpP = x, OpV = y, N->Define(1, m);
}
inline unsigned long long Find(unsigned x) {
  if(!x) return 0;
  OpP = x, N->Qry(1, m);
  return OpV;
}
signed main() {
  n = RD(), mm = (m = RD()) - 1, mm = mm * m % Mod;
  M1 = m - 1, M2 = m - 2, M3 = m - 3, M4 = m - 4, N->Build(1, m);
  for (unsigned i(1); i <= n; ++i) a[i][0] = RD();
  for (unsigned i(1); i <= n; ++i) a[i][1] = RD();
  g[0][0] = g[0][2] = g[0][4] = 1, g[0][1] = g[0][3] = 0;
  for (unsigned i(1); i <= n; ++i) {
    g[i][0] = (g[i - 1][0] * ((M3 * M4 + 1) % Mod) + g[i - 1][1] + g[i - 1][2] + ((g[i - 1][3] + g[i - 1][4]) * M3 << 1) % Mod) % Mod;
    g[i][1] = ((g[i - 1][0] * M2 % Mod) * M3 + g[i - 1][2] + (g[i - 1][4] * M2 << 1)) % Mod;
    g[i][2] = ((g[i - 1][0] * M2 % Mod) * M3 + g[i - 1][1] + (g[i - 1][3] * M2 << 1)) % Mod;
    g[i][3] = ((g[i - 1][0] * M3 % Mod) * M3 + g[i - 1][2] + g[i - 1][3] * M2 + g[i - 1][4] * (M3 + M2)) % Mod;
    g[i][4] = ((g[i - 1][0] * M3 % Mod) * M3 + g[i - 1][1] + g[i - 1][4] * M2 + g[i - 1][3] * (M3 + M2)) % Mod;
  }
  for (unsigned i(1); i <= n; ++i) if(a[i][0] | a[i][1]) {Pos[++Cnt] = i; if(a[i][1]) Flp[Cnt] = 1;}
  for (unsigned i(1); i <= Cnt; ++i) a[i][0] = a[Pos[i]][0], a[i][1] = a[Pos[i]][1];
  if(!Cnt) {
    printf("%llu\n", (Onesided(n - 1) * m % Mod) * M1 % Mod);
    return 0;
  }
  unsigned CT(ColorTwo(1)), Val(Onesided(Pos[1] - 1));
  if(CT) Set(CT, Val);
  else Add(Val), Set(ColorOne(1), 0);
  for (unsigned i(2); i <= Cnt; ++i) {
    unsigned A(a[i - 1][0]), B(a[i - 1][1]), C(a[i][0]), D(a[i][1]), *Len(g[Pos[i] - Pos[i - 1] - 1]);
    if(Flp[i]) swap(A, B), swap(C, D), Flp[i - 1] = (Flp[i - 1] ^ 1);
    if(Flp[i - 1]) {
      if(B == C) {
        unsigned long long PTmp((unsigned long long)N->Sum * Len[4] % Mod);
        Multiple(Mod + Len[2] - Len[4]), Add(PTmp), Set(ColorOne(i), 0);
      } else {
        unsigned long long FC(Find(C)), PTmp(((Mod + N->Sum - FC) * Len[0] + FC * Len[3]) % Mod);
        unsigned long long AB((FC * Len[1] + (Mod + N->Sum - FC) * Len[3]) % Mod);
        Multiple(Mod + Len[4] - Len[0]), Add(PTmp), Set(C, 0), Set(B, AB);
      }
    } else {
      if(A == C) {
        unsigned long long PTmp((unsigned long long)N->Sum * Len[3] % Mod);
        Multiple(Mod + Len[1] - Len[3]), Add(PTmp), Set(ColorOne(i), 0);
      } else {
        unsigned long long FC(Find(C)), PTmp(((Mod + N->Sum - FC) * Len[0] + FC * Len[4]) % Mod);
        unsigned long long AB((FC * Len[2] + (Mod + N->Sum - FC) * Len[4]) % Mod);
        Multiple(Mod + Len[3] - Len[0]), Add(PTmp), Set(C, 0), Set(A, AB);
      }
    }
    if(D) {
      unsigned TmpF(Find(D));
      Multiple(0), Set(D, TmpF);
    }
  }
  CT = ColorOne(Cnt), Ans = N->Sum + Mod - Find(CT);
  printf("%llu\n", Ans * Onesided(n - Pos[Cnt]) % Mod);
  return Wild_Donkey;
}

Day \(13\) Apr 16, 2022, Saturday

SDOI2019 熱鬧的聚會和尷尬的聚會

因為一個方案中, 第一天和第二天是互相獨立的, 因此我們要分別使得 \(p\)\(q\) 最大. 如果 \(p\)\(q\) 分別最大的時候也不能滿足條件, 則無解.

對於 \(p\), 我們用單調佇列從小到大維護每個點的度數, 每次取度最小的點刪掉, 這個點的度數作為此時此刻還沒刪掉的點的集合的 \(p\), 維護一個 \(p\) 的最大值, 和達到這個最大值時刪掉的點集, 輸出時可以將沒刪掉的點輸出.

\(q\) 的最大值顯然是求最大獨立集, 這個問題是經典 NPC, 顯然不能去求 \(q\) 的最大值, 只能求一個近似解.

一開始想的是, 隨便找出一個極大的獨立集, 據說這樣能過. 方法是: 隨機一個排列, 依次選擇, 如果一個點是已經被選的鄰居, 則跳過, 否則選擇.

但是發現每次選一個點, 都會使它當前度數個點不能被選擇, 所以感性地, 每次選擇度數最小的點. 這樣可以證明是一定有解的.

證明是這樣的: 如果第 \(i\) 次選擇的點使 \(Cg_i\) 個點從可以被選擇變成了不能被選擇, 一共選則了 \(q\) 個. 那麼一定有 \(\sum_{i = 1}^q (Cg_i + 1) = n\).

我們知道, 如果第 \(i\) 個被選擇的點使 \(Cg_i\) 個點從可以被選擇變成了不能被選擇, 那麼這個點當時的度數一定大於等於 \(Cg_i\), 而每個點被擴充套件的時候, 一定會更新 \(p\), 也就是說 \(p \geq Cg_i\).

我們需要滿足的條件是 \(\lfloor \frac{n}{p + 1}\rfloor \leq q\), \(\lfloor \frac{n}{q + 1}\rfloor \leq p\), 轉化為 \(\lfloor \frac{n}{p + 1}\rfloor < q + 1\), \(\lfloor \frac{n}{q + 1}\rfloor < p + 1\), 最後是 \(n < (q + 1)(p + 1) + n \% (p + 1)\), \(n < (q + 1)(p + 1) + n \% (q + 1)\). 一個更嚴格的界是 \(n < (q + 1)(p + 1)\), 拆括號可得 \(n < pq + p + q + 1\).

因為 \(p \geq Cg_i\), 所以 \(pq + q \geq \sum_{i = 1}^q (Cg_i + 1) = n\), 因此 \(pq + q + p \geq n\), \(pq + q + p + 1 > n\).

證畢.

因為求獨立集的過程也是每次找一個度數最小的點, 因此可以從求 \(p\) 的過程中同時處理.

struct Node {
  vector<Node*> E;
  Node**Pos; 
  unsigned Deg, Ava;
}N[10005], *Stack[10005], **Head[10005];
vector <Node*> Buck[10005];
unsigned Arr[10005], Cnt(0);
unsigned m, n;
unsigned A, B, C, D, t;
unsigned Ans(0), Tmp(0);
inline void Clr() {
  for (unsigned i(1); i <= n; ++i) N[i].E.clear(), N[i].Ava = 0;
  for (unsigned i(1); i <= n; ++i) Buck[i].clear();
  n = RD(), m = RD(), Head[0] = Stack;
}
signed main() {
  t = RD();
  for (unsigned T(1); T <= t; ++T){
    Clr();
    for (unsigned i(1); i <= m; ++i) 
      A = RD(), B = RD(), N[A].E.push_back(N + B), N[B].E.push_back(N + A);
    for (unsigned i(1); i <= n; ++i) Buck[N[i].Deg = N[i].E.size()].push_back(N + i);
    for (unsigned i(1); i <= n; ++i) {
      Head[i] = Head[i - 1]; 
      for (auto j:Buck[i]) *(++Head[i]) = j, j->Pos = Head[i];
    }
    Ans = 0, A = 1, Cnt = 0;
    for (unsigned i(1); i <= n; ++i) {
      Node* Cur(Stack[i]);
      if(Ans < Cur->Deg) A = i, Ans = Cur->Deg;
      if(!(Cur->Ava)) Arr[++Cnt] = Cur - N;
      for (auto j:Cur->E) if(j->Deg) {
        j->Ava |= (Cur->Ava ^ 1);
        swap(*(++Head[--(Cur->Deg)]), *(Cur->Pos));
        swap(Cur->Pos, (*(Cur->Pos))->Pos);
        swap(*(++Head[--(j->Deg)]), *(j->Pos));
        swap(j->Pos, (*(j->Pos))->Pos);
      }
    }
    printf("%u", n - A + 1);
    for (unsigned i(A); i <= n; ++i) printf(" %u", Stack[i] - N); putchar(0x0A);
    printf("%u", Cnt);
    for (unsigned i(1); i <= Cnt; ++i) printf(" %u", Arr[i]); putchar(0x0A);
  }
  return Wild_Donkey;
}

Day \(14\) Apr 17, 2022, Sunday

今天模擬省選聯考 2022, 起晚了, 少考了半個多小時. 先看了題, T1 不愧是個模擬, 不過沒有那麼恐怖的樣子, T2 還算正常, T3 一眼看出超出能力範圍.

所以先做 T2. 如果列舉一個值域區間, 使得所有非零的點值都在區間內, 我們可以進行樹形 DP 就可以 \(O(n)\) 求出我們想要的東西. 一開始把題讀錯了, 讀成了只要能走就不能停, 這個比原問題還要強, 方案數和總和各需要多記一個狀態, 用了一段時間竟然幹出來了. 發現讀錯題了, 刪掉兩個狀態搞出來了.

一開始是離散化容斥, 結果被第二個樣例卡掉. 改成值域枚舉發現還是錯, 因為我是按值域長度和 \(K\) 的關係容斥的, 仔細思考發現不用列舉每一個長度的區間, 只要對長度為 \(K + 1\)\(K\) 的區間進行容斥, 求 \(O(V)\) 個區間的答案即可求出. 這就是 \(O(nV)\) 的做法.

PrSl2022 前處理器

T2 過大樣例之和開始做 T1.

聽了這道題的臭名聲, 本來以為是個折磨大模擬, 沒想到一會就寫完了, 小調過大樣例. 果然人越菜越會叫喚.

拒絕 string, 雜湊天下第一, 遠離超時.

inline char Judge(char x) {
  if(x >= 'a' && x <= 'z') return 1;
  if(x >= 'A' && x <= 'Z') return 1;
  if(x >= '0' && x <= '9') return 1;
  return x == '_'; 
}
struct Modle {
  unsigned Leng;
  char Cond, Conte[105];
}List[105];
unordered_map<unsigned long long, unsigned> M;
unsigned n, Cnt(0);
char Cur[105];
inline void Open(char* f, unsigned Len) {
  unsigned long long Hash(0);
  unsigned j(0);
  for(unsigned i(0); i < Len; ++i) {
    if(Judge(f[i])) Hash *= Base, Hash += f[i];
    else {
      unsigned Me(M[Hash]);
      if((!Me) || (List[Me].Cond)) while (j <= i) putchar(f[j++]);
      else {
        List[Me].Cond = 1, Open(List[Me].Conte, List[Me].Leng);
        List[Me].Cond = 0, j = i + 1, putchar(f[i]);
      }
      Hash = 0;
    }
  }
  unsigned Me(M[Hash]);
  if((!Me) || (List[Me].Cond)) while (j < Len) putchar(f[j++]);
  else {
    List[Me].Cond = 1, Open(List[Me].Conte, List[Me].Leng);
    List[Me].Cond = 0;
  }
}
int main() {
  n = RD();
  for (unsigned i(1); i <= n; ++i) {
    memset(Cur, 0, sizeof(Cur));
    cin.getline(Cur, 102);
    unsigned Len(strlen(Cur));
    if(Cur[0] == '#') {
      unsigned long long Hash(0);
      unsigned Pos;
      if(Cur[1] == 'd') {
        Pos = 8;
        while (Cur[Pos] != ' ') Hash *= Base, Hash += Cur[Pos++];
        M[Hash] = ++Cnt, ++Pos;
        while (Pos < Len) List[Cnt].Conte[(List[Cnt].Leng)++] = Cur[Pos++];
      } else {
        Pos = 7;
        while (Pos < Len) Hash *= Base, Hash += Cur[Pos++];
        List[M[Hash]].Cond = 1;
      }
    } else Open(Cur, Len);
    putchar(0x0A);
  }
}

Day \(15\) Apr 18, 2022, Monday

模擬省選 2022 Day 2, 今天上來就發現之前快讀寫錯了:

inline unsigned RD() {
  unsigned RTmp(0);
  char ch(getchar()); 
  while (ch < '0' && ch > '9') ch = getchar();
  while (ch >= '0' && ch <= '9') RTmp = RTmp * 10 + ch - '0', ch = getchar(); 
  return RTmp;
}

這個地方

while (ch < '0' && ch > '9') ch = getchar();

Day 1 得分玄學了.

今天感冒了, 半個小時讀完題, 感覺可以先搞一搞 T2, 於是睡了兩個小時.

起來以後發現 T2 的所有權值相等和 \(x = 0\) 可以做. 對於 \(x = y = 1\) 的情況, 我本來寫了一個自認為是正確的演算法, 但是和大樣例差了一點點.

T1 我寫了個爆搜容斥, 可能有個 \(10\)\(20\) 分的吧.

T3 沒看.