省队集训Ⅱ-Day3

Day 3

今天本来能和 23 个人并列第 7, 但是可惜我最后害怕炸了加了一句特判, 挂了 30, 可惜.

Zuiqianyande

定义 fxx 所有因子的亦或和, 求 f1fn 的异或和.

本来能拿三天以来第一个正解, 还是被一句特判杀了...(又是精准控分, 唯一的 70)

unsigned long long n, m, Ans(0), The(0), Sq;
inline unsigned long long Sum (unsigned long long x) {
  if(x & 1) {
    return ((x >> 1) & 1) ? 0 : 1;
  }
  else {
    return x + ((x >> 1) & 1);
  }
}
int main() {
  n = RD();
  Sq = floor(sqrt(n)); 
  for (register unsigned long long i(1); i <= Sq; ++i) {
    if ((n / i) & 1) {
      Ans ^= i;
    }
    if(i & 1) {
      Ans ^= Sum(n / i) ^ Sum(n / (i + 1));
    }
  }
  if(((n / Sq) & 1) && (Sq & 1)) {
    if(Sq * Sq == n) {  // 这一句删了就 A 了
      Ans ^= Sq;
    }
  }
  printf("%llu\n", Ans);
  return 0;
}

这应该是这次培训最短的正解了, 由于考场代码就是正解, 所以我只好压压行, 卡卡常, 然后放出最优解:

unsigned long long n, m, Ans(0), The(0), Sq;
inline unsigned long long Sum(const unsigned long long &x) {
  return ((x & 1) ? (((x >> 1) & 1) ? 0 : 1) : (x + ((x >> 1) & 1)));
}
int main() { 
  n = RD(), Sq = floor(sqrt(n)); 
  for (register unsigned long long i(1); i <= Sq; ++i) {
    if ((n / i) & 1) Ans ^= i;
    if (i & 1) Ans ^= Sum(n / i) ^ Sum(n / (i + 1));
  }
  if(((n / Sq) & 1) && (Sq & 1)) Ans ^= Sq;
  printf("%llu\n", Ans);
  return 0;
}

做这题一开始就读错题了, 以为让求 fn, 我一看这个不是 O(n) 的红题吗? 于是 5min 写出了 "正解", 1014 不是随便 AC.

结果我一测大样例就傻了, 和答案只差一点点, 我觉得是我边界没处理好, 打表一看, 卧槽, 手玩都玩错了. 一看样例解释才发现我假了, 原来的 O(n) 变成 O(nn), 实测只有 35.

手玩样例时发现了规律, 打表证明之:

如果把出现在答案中的异或和加数都写出来, 那么这些加数都小于等于 n, 发现在 n%2=1, 时, 答案的异或加数中有 1 出现, 在 (n2)%4<2 时, 答案中有 2, 在 (ni)%2i<i 时, 答案中有 i 出现. 整理得 n/i 是奇数时, 答案中有 i 出现. 这样只要枚举 [1,n] 中每个数, 判断是否包含即可, 这样就得到了 O(n) 的算法, 实测 45.

然后就放弃 T1 了, 12:00 后面都写不动, 再回来看时, 只能打表, 发现了不得了的东西.

因为在 i 很大的时候, i 的变化很少会影响 n/i 的值, 所以可以预见, 大部分的枚举, 出现在答案里的 i 是连续的段. 容易发现 n/i 变化比 i 缓慢的这个临界点, 恰恰就是 n. 我们枚举 i 枚举到 n, 然后枚举 n/i, 所以我们总的枚举的数量是 O(n).

这种思想就是数论分块, 我从来都没写过, 所以当时就不想写了, 这只是一个黄神午饭时给我口胡的一个算法, 我当时听的时候都感到疑惑, 更别说已经过去了半年, 我已经不记得多少了.

但是后面两题都不会, 我还能干什么呢? 将数论分块写了出来, 过了小样例.

但是这时, 我的每个连续的块是一个个枚举的, 所以我的复杂度是 O(n)O(n)=O(n), 还是 45, 一点都没多, 因为我不会快速求一个整数区间的异或和. 最后 30min, 我只好打表, 发现 1x 前缀的前缀异或和可以写成:

x          x0(mod4)1          x1(mod4)x+1          x2(mod4)0          x3(mod4)

这样就能 O(1) 查询 1x 的前缀异或和 Sumx 了, 如果要求 [l,r] 的整数的异或和, 根据异或的性质, 只要求 SumrSuml1, O(1), 将 O((n)2) 算法优化到 O(n)

Zhengfangguilv

这个题给了一堆正交的线段, 求作为桥的线段.

一开始想用扫描线, 但是发现没有卵用, 因为我只会 O(n2), 并且没法优化.

考虑在所有相交的线段之间连边, 则建成一个二分图. 在二分图上的割点对应的线段就是桥. 而每个度小于等于 1 的点都不是割点. 考虑度更大的点, 只要它连接的边中, 至少有一个点的度为 1, 则这个点是割点. 在 O(n2) 连边后, O(m) 遍历整个图, 就能找到所有的割点. 由于二分图的边数 m 的复杂度是 O(n2) 的, 所以程序的复杂度是 n2 的.

接下来是比较简单的 30 代码:

unsigned n, m, Ans[100005][2], List[1005], Top(0), Fa[10005];
char flg(0);
struct Line {
  unsigned L, R, Pos; char Type; 
}V[100005], H[100005];
struct Edge;
struct Node {
  Edge *Fst; unsigned Dgre;
}N[100005];
struct Edge {
  Node *To; Edge *Nxt;
}E[2000005], *CntE(E);
inline void Link(Node *x, Node *y) {
  (++CntE)->Nxt = x->Fst, x->Fst = CntE, CntE->To = y; 
}
inline unsigned Find(unsigned x) {
  register unsigned Tmp;
  while(Fa[Tmp] ^ Tmp) Tmp = Fa[Tmp];
  return Fa[x] = Tmp;
}
int main() {
  n = RD();
  for (register unsigned i(1); i <= n; ++i)
    H[i].L = RD(), H[i].R = RD();
  for (register unsigned i(1); i <= n; ++i)
    V[i].L = RD(), V[i].R = RD();
  for (register unsigned i(1); i <= n; ++i)
    for (register unsigned j(V[i].L); j <= V[i].R; ++j)
      if(H[j].L <= i && H[j].R >= i) {
        Link(N + i, N + n + j);
        Link(N + n + j, N + i);
        ++(N[i].Dgre);
        ++(N[n + j].Dgre);
      }
  for (register unsigned i(1); i <= n; ++i)
    if(N[i].Dgre >= 2) {
      register Edge *Sid(N[i].Fst);
      while(Sid) {
        if(Sid->To->Dgre <= 1) Ans[i][1] = 1;
        Sid = Sid->Nxt;
      }
    }
  for (register unsigned i(1); i <= n; ++i)
    if(N[i + n].Dgre >= 2) {
      register Edge *Sid(N[i + n].Fst);
      while(Sid) {
        if(Sid->To->Dgre <= 1) Ans[i][0] = 1;
        Sid = Sid->Nxt;
      }
    }
  for (register unsigned i(1); i <= n; ++i) putchar('0' + (Ans[i][0]));
  putchar('\n');
  for (register unsigned i(1); i <= n; ++i) putchar('0' + (Ans[i][1]));
  return 0;
}

正解要用数据结构优化连边, O(nlogn) 维护边, 不再把每一条边都存起来.

Qiguanshaizi

这个题要维护一个染色的树, 支持:

单点修改

同色块修改

查询点的同色块颜色, 点数, 高度(深度极差 + 1).

我场上没有想到什么能用的方法, 只能写一个暴力. 但是调试的过程非常漫长, 因为要考虑的边界过于复杂, 由于子任务难度梯度过大, 以至于我毫无悬念地拿了 20.

unsigned n, m, A, B, C, D;
char flg(0);
struct Node {
  Node *Fa, *Son, *Bro, *Root;
  unsigned Dep, Siz, QwQ;
  char Col;
}N[100005];
inline void Udt(Node *x) {
  x->QwQ = x->Siz = 1;
  register Node *now(x->Son);
  while(now) {
    if(now->Col == x->Col)
      x->Siz += now->Siz, x->QwQ = max(x->QwQ, (now->QwQ) + 1);
    now = now->Bro;
  }
  return;
}
void DFS(Node *x) {
  x->QwQ = x->Siz = 1;
  register Node *now(x->Son);
  while(now) {
    if(now->Col == x->Col) now->Root = x->Root;
    else now->Root = now;
    now->Dep = x->Dep + 1, DFS(now);
    if(now->Col == x->Col)
      x->Siz += now->Siz, x->QwQ = max(x->QwQ, (now->QwQ) + 1);
    now = now->Bro;
  }
  return;
}
void DFS1(Node *x) {
  x->QwQ = x->Siz = 1;
  register Node *now(x->Son);
  while(now) {
    if(now->Col == x->Col) now->Root = x->Root, DFS1(now), x->Siz += now->Siz, x->QwQ = max(x->QwQ, (now->QwQ) + 1);
    else now->Root = now, DFS1(now);
    now = now->Bro;
  }
  return;
}
void DFS2(Node *x) {
  register Node *now(x->Son);
  while(now) {
    if(now->Col == x->Col) DFS2(now), now->Col = C;
    now = now->Bro;
  }
  return;
}
void DFS3(Node *x) {
  x->QwQ = x->Siz = 1;
  register Node *now(x->Son);
  while(now) {
    if(now->Col == x->Col)
      now->Root = x->Root, DFS3(now), x->Siz += now->Siz, x->QwQ = max(x->QwQ, now->QwQ + 1);
    now = now->Bro;
  }
  return;
}
int main() {
  n = RD(), RD(); 
  for (register unsigned i(2); i <= n; ++i)
    N[i].Fa = N + RD(), N[i].Bro = N[i].Fa->Son, N[i].Fa->Son = N + i;
  for (register unsigned i(1); i <= n; ++i) N[i].Col = RD();
  N[1].Dep = 0, N[1].Fa = N, N[1].Root = N + 1, DFS(N + 1);
  m = RD();
  for (register unsigned i(1); i <= m; ++i) {
    A = RD(), B = RD();
    switch(A) {
      case(1): {
        C = RD();
        if(C == N[B].Col) break;
        if(C == N[B].Fa->Col) N[B].Root = N[B].Fa->Root;
        else N[B].Root = N + B;
        N[B].Col = C, DFS1(N + B), Node *now(N + B);
        while (now) Udt(now), now = now->Fa;
        break;
      }
      case(2): {
        C = RD();
        if(C == N[B].Col) break;
        Node *Dest(N[B].Root);
        DFS2(Dest), Dest->Col = C;
        if(C == Dest->Fa->Col) Dest->Root = Dest->Fa->Root;
        else Dest->Root = Dest;
        DFS3(Dest->Root);
        break;
      }
      case(3): {
        printf("%u %u %u\n", N[B].Col, N[B].Root->Siz, N[B].Root->QwQ);
        break;
      }
    }
  } 
  return 0;
}

正解是每个连通块用一个平衡树维护, 因为颜色数量极少, 所以可以每个点分颜色存儿子 (就是邻接表的首个儿子, 存 30 个), 这样可以更快地遍历所需颜色的儿子, 进行块内搜索. 相当于是维护树套平衡树, 单点修改就是点的分裂, 区间修改就是点的合成, 然后查询的是点的信息.

将平衡树持久化以保证时间复杂度.

Ynoi2015

查区间乘积约数个数.

如果定义 Cnti 是第 i 个质数在当前区间中的出现次数.

Ans=ii[l,r]Cnti1

这样就能用莫队维护 Cnti 数组了.

预处理每个数的所有质因数, 建一个 vector Vi,j 表示数字 i 的第 j 个质因数, 由于在值域为 k 的情况下, 质因数的数量是 O(logk). 所以增量的复杂度 O(logk).

莫队的增量数量复杂度是 O(nm), 所以总复杂度 O(nmlogk).

posted @   Wild_Donkey  阅读(37)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示