2022 年 9 月水题选做

20220901

SP30919 GCDS - Sabbir and gcd problem

思路:显然答案就是不是任意一个数的因数的最小的质数。这个可以在线性筛的时候记录每个数的最小的素因数即可。

算法:线性筛。

技巧:线性筛可以筛每个数的最小的素因数。

想到了的:都想到了。

没想到的:无。

代码
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;

const int N = 1e5, W = 1e7;

int n, a[N + 10];
int prm[W + 10], notPrm[W + 10], totp, b[W + 10], notOk[W + 10];

void sieve() {
  for (int i = 2; i <= W; i++) {
    if (!notPrm[i]) prm[++totp] = i, b[i] = i;
    for (int j = 1; j <= totp && i * prm[j] <= W; j++) {
      notPrm[i * prm[j]] = 1;
      b[i * prm[j]] = prm[j];
      if (i % prm[j] == 0) break;
    }
  }
}

void mian() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    scanf("%d", a + i);
    for (int j = a[i]; j > 1; j /= b[j]) notOk[b[j]] = 1;
  }
  for (int i = 1; i <= totp; i++)
    if (!notOk[prm[i]]) return printf("%d\n", prm[i]), void();
}

int main() {
  sieve();
  int T; scanf("%d", &T);
  while (T--) { memset(notOk, 0, sizeof(notOk)); mian(); }
  return 0;
}

20220905

CF1061C Multiplicity

思路:设 \(f_{i,j}\) 为考虑前 \(i\) 个数,子序列长度为 \(j\) 的方案数,则:

\[f_{i,j}=\begin{cases}f_{i-1,j},&j\nmid a_i\\f_{i-1,j-1}+f_{i-1,j},&j\mid a_i\end{cases} \]

第一维可以压掉。然后我们发现真正要被转移到的地方很少,于是时间复杂度也 OK 了。

算法:dp。

技巧:只考虑有用状态。

想到了的:都想到了。

没想到的:无。

代码
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <functional>
#include <vector>
using namespace std;

const int N = 1e5, P = 1e9 + 7;

int n, a[N + 10], f[N + 10];
vector<int> d[N + 10];

int main() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    scanf("%d", a + i);
    for (int j = 1; j <= int(sqrt(a[i])); j++)
      if (a[i] % j == 0) {
        d[i].push_back(j);
        if (j * j != a[i]) d[i].push_back(a[i] / j);
      }
    sort(d[i].begin(), d[i].end(), greater<int>());
  }
  f[0] = 1;
  for (int i = 1; i <= n; i++)
    for (auto j : d[i])
      if (j <= n) (f[j] += f[j - 1]) %= P;
  int ans = 0;
  for (int i = 1; i <= n; i++)
    (ans += f[i]) %= P;
  printf("%d\n", ans);
  return 0;
}

P2606 [ZJOI2010]排列计数

思路:把不等关系画成一棵树(根为 \(1\))然后 dp。设 \(f_u\) 表示以 \(u\) 为根的子树的答案,则:

\[f_u=f_{2u}f_{2u+1}{siz_{2u}+siz_{2u+1}\choose siz_{2u}} \]

注意模数可以很小,所以求组合数要用 Lucas 定理。

算法:树形 dp、计数、Lucas。

技巧:建立不等关系树。

想到了的:都想到了。

没想到的:无。

代码
#include <algorithm>
#include <cstdio>
#include <tuple>
using namespace std;

const int N = 1e6;

int n, P;
int fac[N + 10], ifac[N + 10];

int qpow(int a, int b) {
  int res = 1;
  while (b) {
    if (b & 1) res = 1LL * res * a % P;
    a = 1LL * a * a % P; b >>= 1;
  }
  return res;
}

void init() {
  int lim = min(N, P - 1);
  fac[0] = 1;
  for (int i = 1; i <= lim; i++)
    fac[i] = 1LL * fac[i - 1] * i % P;
  ifac[lim] = qpow(fac[lim], P - 2);
  for (int i = lim - 1; i >= 0; i--)
    ifac[i] = 1LL * ifac[i + 1] * (i + 1) % P;
}

int comb(int a, int b) {
  if (a < 0 || b < 0 || a < b) return 0;
  return 1LL * fac[a] * ifac[b] % P * ifac[a - b] % P;
}

int C(int a, int b) {
  if (a < P && b < P) return comb(a, b);
  return 1LL * C(a / P, b / P) * comb(a % P, b % P) % P;
}

pair<int, int> f(int x) {
  if (x > n) return {1, 0};
  int la, ls, ra, rs;
  tie(la, ls) = f(x * 2);
  tie(ra, rs) = f(x * 2 + 1);
  return {1LL * la * C(ls + rs, ls) % P * ra % P, ls + rs + 1};
}

int main() {
  scanf("%d%d", &n, &P);
  init();
  printf("%d\n", f(1));
  return 0;
}

CF245H Queries for Number of Palindromes

思路:先用一遍 dp 求出每个子串是不是回文的,然后求一遍这个 dp 数组的二维前缀和。

算法:dp、前缀和。

技巧:回文串问题可以区间 dp。

想到了的:都想到了。

没想到的:无。

代码
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;

const int N = 5000;

int n, m;
char s[N + 10];
bool f[N + 10][N + 10];
int g[N + 10][N + 10];

int main() {
  scanf("%s", s + 1);
  n = int(strlen(s + 1));
  for (int i = 1; i <= n; i++) {
    f[i][i] = 1;
    if (i < n) f[i][i + 1] = s[i] == s[i + 1];
  }
  for (int len = 3; len <= n; len++)
    for (int l = 1; l <= n - len + 1; l++) {
      int r = l + len - 1;
      f[l][r] = s[l] == s[r] && f[l + 1][r - 1];
    }
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= n; j++)
      g[i][j] = g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1] + f[i][j];
  scanf("%d", &m);
  while (m--) {
    int l, r; scanf("%d%d", &l, &r);
    printf("%d\n", g[r][r] - g[l - 1][r] - g[r][l - 1] + g[l - 1][l - 1]);
  }
  return 0;
}

20220907

P6006 [USACO20JAN]Farmer John Solves 3SUM G

思路:显然可以预处理答案。考虑枚举 \(i,k\),那么 \((i,k)\) 可以贡献到 \(l\le i,k\le r\) 的询问,\((i,k)\) 的答案即满足 \(i\lt j\lt k,a_j=-(a_i+a_k)\)\(j\) 的个数。

算法:桶、前缀和。

技巧:预处理答案、考虑贡献。

想到了的:都想到了。

没想到的:无。

代码
#include <algorithm>
#include <cstdio>
using namespace std;

typedef long long ll;

const int N = 5000, W = 2e6;

int n, m, a[N + 10], ccnt[W + 10], *cnt = ccnt + W / 2 + 5;
ll g[N + 10][N + 10];

int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++)
    scanf("%d", a + i);
  for (int i = 1; i <= n; i++) {
    for (int j = i + 1; j <= n; j++) {
      g[i][j] = -W / 2 <= a[i] + a[j] && a[i] + a[j] <= W / 2 && j - i >= 2 ? cnt[-(a[i] + a[j])] : 0;
      cnt[a[j]]++;
    }
    for (int j = i + 1; j <= n; j++)
      cnt[a[j]]--;
  }
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= n; j++)
      g[i][j] = g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1] + g[i][j];
  while (m--) {
    int l, r;
    scanf("%d%d", &l, &r);
    printf("%lld\n", g[r][r] - g[r][l - 1] - g[l - 1][r] + g[l - 1][l - 1]);
  }
  return 0;
}

P6007 [USACO20JAN]Springboards G

思路:下面用 \(m\) 代表题目中的 \(n\)\(n\) 代表题目中的 \(p\)

注意到我们如果不用任何跳板,答案就是 \(2m\),我们可以计算使用跳板能省多少。于是题目就变成了:选择一些跳板,使其满足偏序关系,且能省的距离最大。

显然有一个 \(\mathcal O(n^2)\) dp:对于一个跳板,从它左下角的所有跳板转移过来。考虑优化。

于是这就是一个二维数点。我们可以扫描线维护 \(x\) 轴,树状数组维护 \(y\) 轴;遇到一个起点就更新 dp,遇到一个终点就更新树状数组。

for (int i = 1; i <= 2 * n; i++) {
  if (a[i].type == 0) { // 起点
    f[a[i].id] = query(a[i].y) + dis[a[i].id];
  } else { // 终点
    modify(a[i].y, f[a[i].id]);
  }
}

算法:dp、二维数点、数据结构优化 dp。

技巧:二维数点(扫描线 + 数据结构维护)。

想到了的:dp、数据结构优化。

没想到的:二维数点具体维护方法。

代码
#include <algorithm>
#include <cstdio>
using namespace std;

const int N = 2e5;

struct Node {
  int x, y, id, type;
};

int n, m;
Node a[N + 10];
int dis[N + 10], f[N + 10];
int tx[N + 10], ty[N + 10];
int c[N + 10];

#define lowbit(x) (x & (-x))
void modify(int p, int x) { for (; p <= 2 * n; p += lowbit(p)) c[p] = max(c[p], x); }
int query(int p) { int res = 0; for (; p; p -= lowbit(p)) res = max(res, c[p]); return res; }
#undef lowbit

int main() {
  scanf("%d%d", &m, &n);
  for (int i = 1; i <= n; i++) {
    int x1, y1, x2, y2;
    scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
    a[i * 2 - 1] = {x1, y1, i, 0};
    a[i * 2] = {x2, y2, i, 1};
    tx[i * 2 - 1] = x1, tx[i * 2] = x2;
    ty[i * 2 - 1] = y1, ty[i * 2] = y2;
    dis[i] = x2 - x1 + y2 - y1;
  }
  sort(tx + 1, tx + 2 * n + 1);
  sort(ty + 1, ty + 2 * n + 1);
  int totx = int(unique(tx + 1, tx + 2 * n + 1) - tx - 1);
  int toty = int(unique(ty + 1, ty + 2 * n + 1) - ty - 1);
  for (int i = 1; i <= n; i++) {
    a[i * 2 - 1].x = lower_bound(tx + 1, tx + totx + 1, a[i * 2 - 1].x) - tx;
    a[i * 2 - 1].y = lower_bound(ty + 1, ty + toty + 1, a[i * 2 - 1].y) - ty;
    a[i * 2].x = lower_bound(tx + 1, tx + totx + 1, a[i * 2].x) - tx;
    a[i * 2].y = lower_bound(ty + 1, ty + toty + 1, a[i * 2].y) - ty;
  }
  sort(a + 1, a + 2 * n + 1, [](const Node &lhs, const Node &rhs) {
    return lhs.x == rhs.x ? lhs.y < rhs.y : lhs.x < rhs.x;
  });
  for (int i = 1; i <= 2 * n; i++) {
    if (a[i].type == 0) {
      f[a[i].id] = query(a[i].y) + dis[a[i].id];
    } else {
      modify(a[i].y, f[a[i].id]);
    }
  }
  printf("%d\n", 2 * m - *max_element(f + 1, f + n + 1));
  return 0;
}

CF1139D Steps to One

思路:推式子。

\[\begin{aligned} E(x)&=\sum_{i=1}^{+\infty}P(i)\cdot i\\ &=\sum_{i=1}^{+\infty}P(i)\sum_{j=1}^i 1\\ &=\sum_{j=1}^{+\infty}\sum_{i=j}^{+\infty}P(i)\\ &=\sum_{j=1}^{+\infty}P(\ge j)\\ &=\sum_{j=1}^{+\infty}P(\gt j)+\sum_{j=1}^{+\infty}P(j)\\ &=\sum_{j=1}^{+\infty}P(\gt j)+1 \end{aligned} \]

于是我们继续研究 \(P(\gt k)\)

\[\begin{aligned} P(\gt k)&=P\left(\gcd_{i=1}^k a_i\gt 1\right)\\ &=1-P\left(\gcd_{i=1}^k a_i=1\right)\\ &=1-\frac{\sum_{i_1=1}^m\sum_{i_2=1}^m\cdots\sum_{i_k=1}^m[\gcd(i_1,i_2,\cdots,i_k)=1]}{m^k}\\ \sum_{i_1=1}^m\sum_{i_2=1}^m\cdots\sum_{i_k=1}^m[\gcd(i_1,i_2,\cdots,i_k)=1]&=\sum_{i_1=1}^m\sum_{i_2=1}^m\cdots\sum_{i_k=1}^m\sum_{d\mid\gcd(i_1,i_2,\cdots,i_k)}\mu(d)\\ &=\sum_{d=1}^m\mu(d)\left\lfloor\frac md\right\rfloor^k\\ \therefore P(\gt k)&=1-\frac{\sum_{d=1}^m\mu(d)\left\lfloor\frac md\right\rfloor^k}{m^k}\\ &=-\frac{\sum_{d=2}^m\mu(d)\left\lfloor\frac md\right\rfloor^k}{m^k} \end{aligned} \]

代回原式:

\[\begin{aligned} E(x)&=1-\sum_{i=1}^{+\infty}\frac{\sum_{d=2}^m\mu(d)\left\lfloor\frac md\right\rfloor^i}{m^i}\\ &=1-\sum_{d=2}^m\mu(d)\sum_{i=1}^{+\infty}\left(\frac{\left\lfloor\frac md\right\rfloor}{m}\right)^i\\ &=1-\sum_{d=2}^m\mu(d)\frac{\left\lfloor\frac md\right\rfloor}{m-\left\lfloor\frac md\right\rfloor} \end{aligned} \]

线性筛 \(\mu\) 即可。

算法:期望、莫反。

技巧:莫反套路、\(E(x)=\sum_{i=1}^{+\infty}P(i)\cdot i=\sum_{i=1}^{+\infty}P(\ge i)=1+\sum_{i=1}^{+\infty}P(\gt i)\)

想到了的:莫反。

没想到的:那个期望的结论。

代码
#include <algorithm>
#include <cstdio>
using namespace std;

const int N = 1e5, P = 1e9 + 7;

int n, prm[N + 10], notPrm[N + 10], totp, mu[N + 10];

void sieve() {
  mu[1] = 1;
  for (int i = 2; i <= N; i++) {
    if (!notPrm[i]) prm[++totp] = i, mu[i] = -1;
    for (int j = 1; i * prm[j] <= N; j++) {
      notPrm[i * prm[j]] = 1;
      if (i % prm[j] == 0) { mu[i * prm[j]] = 0; break; }
      mu[i * prm[j]] = -mu[i];
    }
  }
}

int qpow(int a, int b) {
  int res = 1;
  while (b) {
    if (b & 1) res = 1LL * res * a % P;
    a = 1LL * a * a % P; b >>= 1;
  }
  return res;
}

int main() {
  sieve();
  scanf("%d", &n);
  int ans = 0;
  for (int i = 2; i <= n; i++) {
    int q = n / i;
    (ans += 1LL * mu[i] * q % P * qpow(n - q, P - 2) % P) %= P;
  }
  ans = 1 - ans;
  printf("%d\n", (ans % P + P) % P);
}
posted @ 2022-09-05 22:01  registerGen  阅读(85)  评论(0编辑  收藏  举报