Codeforces Round #678


写在前面

Codeforces Round #678 (Div. 2)

比赛地址:https://codeforces.com/contest/1436

脑子犯浑。


A

Link

\(t\) 组数据,每次给定一长度为 \(n\) 的数列 \(a\),参数 \(m\)
判断能否通过重新排列 \(a\),使得下式成立:

\[\sum_{i=1}^{n}\sum_{j=i}^{n} \frac{a_j}{j} = m \]

上式中均为实数运算,不存在取整。
\(1\le t, n\le 100\)\(0\le m,a_i\le 10^6\)
1S,256MB。

无论 \(a\) 如何排列,都有:

\[\sum_{i=1}^{n}\sum_{j=i}^{n} \frac{a_j}{j}=\sum_{i=1}^{n} i\times \frac{a_i}{i} = \sum_{i=1}^{n}a_i \]

判断 \(\sum_{i=1}^{n}a_i = m\) 是否成立即可。

//知识点:结论 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
//=============================================================
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  }
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
//=============================================================
int main() {
  int t = read();
  while (t --) {
    int n = read(), m = read();
    for (int i = 1; i <= n; ++ i) {
      m -= read();
    }
    printf("%s\n", !m ? "YES" : "NO");
  }
  return 0;
}

B

Link

\(t\) 组数据,每次给定参数 \(n\),要求构造一个 \(n\times n\) 的数字矩阵 \(a\),满足下列要求:

  • \(0\le a_{i,j}\le 10^5\)
  • \(a_{i,j}\notin \mathbb{P}\)
  • \(\forall 1\le x\le n\),满足 \(\sum_{i=1}^{n} a_{x,i} \in \mathbb{P}\)\(\sum_{i=1}^{n} a_{i,x} \in \mathbb{P}\)

其中 \(\mathbb {P}\) 表示质数集。
\(1\le t\le 10\)\(2\le n\le 100\)
1.5S,256MB。

怀疑 \(a_{i,j}\le 10^5\) 是因为 SPJ 跑的有点慢。

以下是场上的构造方法。
一个显然想法是使各行各列的和都相等,显然可以构造成这样的形式:

\[\begin{bmatrix} x &1 &1 &1\\ 1 &x &1 &1\\ 1 &1 &x &1\\ 1 &1 &1 &x\\ \end{bmatrix}\]

数据范围不大,暴力找到一个非质数 \(x\),使 \(x +(n-1)\in \mathbb {P}\) 即可。

//知识点:构造
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int p_num, p[kN], las[kN];
bool vis[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  }
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void Prepare() {
  for (int i = 2; i <= 100000; ++ i) {
    if (! vis[i]) p[++ p_num] = i;
    for (int j = 2 * i; j <= 100000; j += i) {
      vis[j] = true;
    }
  }
}
//=============================================================
int main() {
  Prepare();
  int t = read();
  while (t --) {
    int n = read(), ans;
    for (int i = 1; i <= p_num; ++ i) {
      ans = p[i];
      if (ans <= (n - 1)) continue ;
      if (vis[ans - (n - 1)]) break ;
    }
    for (int i = 1; i <= n; ++ i) {
      for (int j = 1; j <= n; ++ j) {
        if (i == j) {
          printf("%d ", ans - (n - 1));
        } else {
          printf("%d ", 1);
        }
      }
      printf("\n");
    }
  }
  return 0;
}

C

Link

以下是一份在下标从 \(0\) 开始的数组 \(a\) 中二分查找 \(x\) 是否存在的代码:
pic
给定参数 \(n,x,pos\),求有多少个 \(1\sim n\) 的排列,满足在排列中进行上述二分查找算法,最后能在 \(pos\) 位置找到 \(x\),答案对 \(10^9+7\) 取模。
\(1\le x\le n\le 10^3\)\(0\le pos\le n-1\)
1S,256MB。

已知查找的终止状态,可以还原出二分各步访问到的位置,以及它们与 \(x\) 的相对大小关系。
\(a\) 表示访问到的位置中 小于 \(x\) 的位置的数量,\(b\) 表示 大于 \(x\) 的数量,答案显然为:

\[A(x-1, a)\cdot A(n-x, b)\cdot A(n-a-b-1, n-a-b-1)\pmod {10^9+7} \]

给出的程序中出现 \(a_{mid}=x\) 时仍然会继续二分,代码里的 down 实际上表示 小于等于 \(x\) 的位置的数量。

时间复杂度 \(O(n)\)

//知识点:组合数学 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const LL mod = 1e9 + 7;
const LL kN = 1e3 + 10;
//=============================================================
int n, x, pos;
LL down, up, fac[kN], invfac[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  }
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
LL QPow(LL x_, LL y_) {
  LL ret = 1;
  for (; y_; y_ >>= 1) {
    if (y_ & 1) ret = ret * x_ % mod;
    x_ = x_ * x_ % mod;
  }
  return ret;
}
LL A(LL n_, LL m_) {
  if (n_ < 0 || m_ < 0 || n_ < m_) return 0ll;
  return fac[n_] * invfac[n_ - m_] % mod;
}
//=============================================================
int main() {
  invfac[0] = fac[0] = 1;
  n = read(), x = read(), pos = read();
  for (int i = 1; i <= n; ++ i) {
    fac[i] = 1ll * fac[i - 1] * i % mod;
//    invfac[i] = QPow(fac[i], mod - 2);
  }
  invfac[n] = QPow(fac[n], mod - 2);
  for (int i = n - 1; i >= 1; -- i) {
    invfac[i] = 1ll * invfac[i + 1] * (i + 1) % mod;
  }
  
  int l = 0, r = n;
  while (l < r) {
    int mid = (l + r) / 2;
    if (pos >= mid) {
      down ++;
      l = mid + 1;
    } else {
      up ++;
      r = mid;
    }
  }
  
  LL ans = A(x - 1, down - 1) * A(n - x, up) % mod;
  ans = ans * A(n - down - up, n - down - up) % mod;
  printf("%lld\n", ans);
  return 0;
}

D

Link

给定一棵 \(n\) 个节点的有根树,根为 \(1\),第 \(i\) 个节点上有 \(a_i\) 个人。
每个人可以往任意子节点走,直到走到叶节点,求最后人最多的叶节点的最少人数。
\(2\le n\le 2\times 10^5\)\(0\le a_i\le 10^9\)
1S,256MB

场上乱搞了个假 DP 吃了两发= =

\(\operatorname{sum}_{u}\) 表示 \(u\) 子树中所有节点的人数之和,\(\operatorname{leaf}_u\) 表示 \(u\) 子树中叶节点的个数。
首先考虑最理想状态,对于节点 \(u\),若它子树中的所有人都能均匀地散布在所有叶节点中,则显然该子树中 人最多的叶节点的人数为 \(\left\lceil \frac{\operatorname{sum}_u}{\operatorname{leaf}_u} \right\rceil\)

但一般无法达到理想状态,设 \(f_{u}\) 表示以 \(u\) 为根的子树中人最多的叶节点的人数。对于节点 \(u\) 的某儿子 \(v\),显然,存在 \(f_{v} > \left\lceil \frac{\operatorname{sum}_u}{\operatorname{leaf}_u} \right\rceil\) 时无法均分。
反之,当 \(\forall v\in son(u),\ f_v\le \left\lceil \frac{\operatorname{sum}_u}{\operatorname{leaf}_u} \right\rceil\) 时,可以将 \(a_u\) 按一定方案分配到各叶节点,形成均匀散布的形式。

对于所有叶节点 \(u\),初始化 \(\operatorname{leaf}_u = 1\),则有显然的状态转移方程:

\[\begin{aligned} \operatorname{sum}_u &= a_u + \sum_{v\in son(u)} \operatorname{sum}_v\\ \operatorname{leaf}_u &= \sum_{v\in son(u)} \operatorname{leaf}_v\\ f_{u} &= \max\left\{ \max_{v\in son(u)}\{f_v\},\ \left\lceil \frac{\operatorname{sum}_u}{\operatorname{leaf}_u} \right\rceil\right\} \end{aligned}\]

答案即为 \(f_1\)

算法总时间复杂度 \(O(n)\)

还有种被卡 ull 的暴力二分答案,感兴趣的可以看下 Luogu 题解。

//知识点:树形DP 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <vector>
#include <cmath>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, e_num, a[kN], head[kN], v[kN], ne[kN];
LL leaf[kN], sum[kN], f[kN];
bool fa[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  }
  return f * w;
}
void Chkmax(LL &fir_, LL sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(LL &fir_, LL sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_) {
  v[++ e_num] = v_;
  ne[e_num] = head[u_];
  head[u_] = e_num;
}
void Dfs(int u_) {
  if (! fa[u_]) leaf[u_] = 1;
  sum[u_] = a[u_];
  
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    Dfs(v_);
    Chkmax(f[u_], f[v_]);
    sum[u_] += sum[v_];
    leaf[u_] += leaf[v_];
  }
  Chkmax(f[u_], ceil(1.0 * sum[u_] / leaf[u_]));
}
//=============================================================
int main() {
  n = read();
  for (int v_ = 2; v_ <= n; ++ v_) {
    int u_ = read();
    AddEdge(u_, v_);
    fa[u_] = true;
  }
  for (int i = 1; i <= n; ++ i) a[i] = read();
  Dfs(1);
  printf("%lld\n", f[1]);
  return 0;
}

E

Link

给定一长度为 \(n\) 的数列 \(a\),求其所有连续子序列的 \(\operatorname{mex}\)\(\operatorname{mex}\)
\(\operatorname{mex}(S)\) 定义为数集 \(S\) 中最小的没有出现的 正整数
\(1\le a_i\le n\le 10^5\)
1S,256MB。

设答案为 \(ans\),显然答案符合下列性质:

  • 没有连续子序列的 \(\operatorname{mex}\)\(ans\)
  • \(1\sim ans-1\) 都能够作为某连续子序列的 \(\operatorname{mex}\) 出现。

先考虑如何判断性质 1。
若某连续的子序列的 \(\operatorname{mex}\)\(ans\),显然该连续子序列中不包括数 \(ans\),且连续子序列越长,越有可能满足条件。
考虑枚举所有不含 \(ans\) 的极长连续子序列进行判断,发现这些连续子序列即为整个数列 被所有 \(ans\) 分割成的子段。查询这些子段的 \(\operatorname{mex}\),判断是否等于 \(ans\) 即可。

再考虑性质 2,发现只需要从小到大枚举答案,重复对性质 1 的判断即可。
枚举到的第一个不满足性质 1 的即为答案。

考虑实现。
对于一个在数列中出现了 \(k\) 次的数,它可将数列分为 \(k+1\) 段,则总查询 \(\operatorname{mex}\) 次数为 \(O(n)\) 级别。
使用主席树,总时间复杂度为 \(O(n\log n)\) 级别。
使用莫队 + 值域分块,总时间复杂度 \(O(n\sqrt m + m\sqrt n)\)

怎么用主席树实现啊/jk
\(i\) 棵主席树的叶节点 \(x\) 储存权值 \(x\)\(a_{1}\sim a_{i}\) 中最晚出现的位置。 插入时只有单点修改,因此可以可持久化。
查询时查询第 \(r\) 棵主席树中最小的,最晚出现位置 \(<l\) 的权值,即为区间 \([l,r]\)\(\operatorname{mex}\)。线段树维护区间最小值,主席树上二分即可。

//知识点:结论,主席树 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <vector>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, a[kN], root[kN];
std::vector <int> pos[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  }
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
namespace Hjt {
  #define ls (lson[now_])
  #define rs (rson[now_])
  #define mid (L_+R_>>1)
  int node_num, lson[kN << 5], rson[kN << 5], t[kN << 5];
  void Pushup(int now_) {
    t[now_] = std::min(t[ls], t[rs]);
  }
  void Insert(int &now_, int pre_, int L_, int R_, int pos_, int val_) {
    now_ = ++ node_num;
    if (L_ == R_) {
      t[now_] = val_;
      return ;
    }
    ls = lson[pre_], rs = rson[pre_];
    t[now_] = t[pre_];
    if (pos_ <= mid) Insert(ls, lson[pre_], L_, mid, pos_, val_);
    else Insert(rs, rson[pre_], mid + 1, R_, pos_, val_);
    Pushup(now_);
  }
  int Query(int now_, int L_, int R_, int pos_) {
    if (L_ == R_) return L_;
    if (t[ls] < pos_) return Query(ls, L_, mid, pos_);
    return Query(rs, mid + 1, R_, pos_);
  }
  #undef ls
  #undef rs
  #undef mid
}
//=============================================================
int main() {
  n = read();
  for (int i = 1; i <= n; ++ i) {
    a[i] = read();
    pos[a[i]].push_back(i);
    Hjt::Insert(root[i], root[i - 1], 1, n + 1, a[i], i);
  }
  for (int i = 1; i <= n + 1; ++ i) pos[i].push_back(n + 1);
  for (int i = 1; i <= n + 2; ++ i) {
    int lim = pos[i].size(), flag = 0;
    for (int j = 0, las = 1; j < lim; ++ j) {
      int now = pos[i][j];
      if (las < now) {
        flag = (Hjt::Query(root[now - 1], 1, n + 1, las) == i);
        if (flag) break;
      }
      las = now + 1;
    }
    if (! flag) {
      printf("%d\n", i);
      break;
    }
  }
  return 0;
}

F

感觉是个 nb 题,所以先咕掉了。

总结

  • \(1\sim n\) 的阶乘的逆元可以 \(O(\log n + n)\) 地处理出来。
  • 少一点分割线 markdown 会更好看。
  • 只要是单点修改的信息都可以进行可持久化。
posted @ 2020-11-12 11:22  Luckyblock  阅读(91)  评论(0编辑  收藏  举报