逐月資訊學 2024 提高組 #2

Yaosicheng124發表於2024-07-05

\(\color{black}\texttt{A. 序列}\)

題目描述

給定 \(N\) 個數,每個數均可寫成 \(pq(p,q\in\mathbb{P},p<q)\) 的形式,問最長能找到多長的子序列使得任意相鄰兩項 \(x_i=p_1q_1,x_{i+1}=p_2q_2(p_1,q_1,p_2,q_2\in\mathbb{P},p_1<q_1,p_2<q_2)\) 滿足 \(q_1=p_2\)

思路

按照 \(p\) 排序並 dp 即可。

程式碼

#include<bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;

const int MAXN = 50001;

struct Num {
  int p, q;
}a[MAXN];

int n, Max[MAXN], ans;

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n;
  for(int i = 1, x; i <= n; ++i) {
    cin >> x;
    for(int j = 2; j * j <= x; ++j) {
      if(x % j == 0) {
        a[i] = {j, x / j};
        break;
      }
    }
  }
  sort(a + 1, a + n + 1, [](const Num &x, const Num &y) { return x.p < y.p || (x.p == y.p && x.q < y.q); });
  for(int i = 1; i <= n; ++i) {
    ans = max(ans, Max[a[i].p] + 1);
    Max[a[i].q] = max(Max[a[i].q], Max[a[i].p] + 1);
  }
  cout << ans;
  return 0;
}

\(\color{black}\texttt{B. 生成最小樹}\)

題目描述

給定一張圖和其中的一棵樹,每次操作可以將一條邊的邊權 \(-1\),求最少需要多少次操作才能使這棵樹變成最小生成樹?

思路

因為每一條非樹邊必須 \(\ge\) 邊兩個端點樹上路徑的每一條邊,不然這條邊一定比樹邊更優,所以使用樹鏈剖分求出每條邊的限制即可。

程式碼

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;

const int MAXN = 10001, MAXM = 100001;

struct Edge {
  int u, v, w;
}g[MAXM];

struct Segment_Tree {
  int l[4 * MAXN], r[4 * MAXN], dfn[MAXN], W[MAXN], Min[4 * MAXN], lazy[4 * MAXN];
  void build(int u, int s, int t) {
    l[u] = s, r[u] = t, lazy[u] = INT_MAX;
    if(s == t) {
      Min[u] = W[dfn[s]];
      return;
    }
    int mid = (s + t) >> 1;
    build(2 * u, s, mid), build(2 * u + 1, mid + 1, t);
    Min[u] = min(Min[2 * u], Min[2 * u + 1]);
  }
  void tag(int u, int x) {
    Min[u] = min(Min[u], x), lazy[u] = min(lazy[u], x);
  }
  void pushdown(int u) {
    tag(2 * u, lazy[u]), tag(2 * u + 1, lazy[u]), lazy[u] = INT_MAX;
  }
  void update(int u, int s, int t, int x) {
    if(l[u] >= s && r[u] <= t) {
      tag(u, x);
      return;
    }
    pushdown(u);
    if(s <= r[2 * u]) {
      update(2 * u, s, t, x);
    }
    if(t >= l[2 * u + 1]) {
      update(2 * u + 1, s, t, x);
    }
    Min[u] = min(Min[2 * u], Min[2 * u + 1]);
  }
  int Get(int u, int p) {
    if(l[u] == r[u]) {
      return Min[u];
    }
    pushdown(u);
    return (p <= r[2 * u] ? Get(2 * u, p) : Get(2 * u + 1, p));
  }
}tr;

int n, m, sz[MAXN], f[MAXN], son[MAXN], dfn[MAXN], tot, top[MAXN];
ll ans;
vector<pii> e[MAXN];
map<pii, bool> vis;
map<pii, int> _W;

void dfs(int u, int fa) {
  f[u] = fa, sz[u] = 1;
  for(auto [v, w] : e[u]) {
    if(v != fa) {
      tr.W[v] = w;
      dfs(v, u);
      sz[u] += sz[v];
      if(sz[v] > sz[son[u]]) {
        son[u] = v;
      }
    }
  }
}

void DFS(int u, int fa) {
  dfn[u] = ++tot, tr.dfn[tot] = u;
  if(son[u]) {
    top[son[u]] = top[u], DFS(son[u], u);
  }
  for(auto [v, w] : e[u]) {
    if(v != fa && v != son[u]) {
      top[v] = v, DFS(v, u);
    }
  }
}

void changepath(int u, int v, int w) {
  for(; top[u] != top[v]; ) {
    if(dfn[u] > dfn[v]) {
      tr.update(1, dfn[top[u]], dfn[u], w);
      u = f[top[u]];
    }else {
      tr.update(1, dfn[top[v]], dfn[v], w);
      v = f[top[v]];
    }
  }
  if(min(dfn[u], dfn[v]) + 1 <= max(dfn[u], dfn[v])) {
    tr.update(1, min(dfn[u], dfn[v]) + 1, max(dfn[u], dfn[v]), w);
  }
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for(int i = 1; i <= m; ++i) {
    cin >> g[i].u >> g[i].v >> g[i].w;
    _W[{g[i].u, g[i].v}] = _W[{g[i].v, g[i].u}] = g[i].w;
  }
  for(int i = 1, u, v; i < n; ++i) {
    cin >> u >> v;
    e[u].push_back({v, _W[{u, v}]});
    e[v].push_back({u, _W[{u, v}]});
    vis[{u, v}] = vis[{v, u}] = 1;
  }
  dfs(1, 0);
  top[1] = 1;
  DFS(1, 0);
  tr.build(1, 1, n);
  for(int i = 1; i <= m; ++i) {
    if(!vis.count({g[i].u, g[i].v})) {
      changepath(g[i].u, g[i].v, g[i].w);
    }
  }
  for(int i = 2; i <= n; ++i) {
    ans += max(0, _W[{f[i], i}] - tr.Get(1, dfn[i]));
  }
  cout << ans;
  return 0;
}

\(\color{black}\texttt{C. 互質序列}\)

題目描述

給定 \(l,r\),求有多少個序列滿足以下條件:

  • 序列單調遞增。
  • 序列中的數 \(\in[l,r]\)
  • 序列中的數兩兩互質。

思路

因為 \(r-l+1\le 100\),所以出現超過兩次的質數一定 \(\le 100\),所以狀壓 dp 即可。

但是這樣可能會 \(\texttt{TLE}\),所以要先預處理出所有出現超過一次的質數。

\(V=25\),空間複雜度 \(O(2^V)\),時間複雜度 \(O((r-l)2^V)\)

程式碼

#include<bits/stdc++.h>
using namespace std;
using ll = long long;

const int prime[] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97};

ll a, b, dp[1 << 25], ans;
vector<int> v;

void FileIO(const string &s) {
  freopen((s + ".in").c_str(), "r", stdin);
  freopen((s + ".out").c_str(), "w", stdout);
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  FileIO("rlprime");
  cin >> a >> b;
  for(int i = 1; i <= 25; ++i) {
    int cnt = 0;
    for(ll j = a; j <= b; ++j) {
      cnt += (j % prime[i] == 0);
    }
    if(cnt > 1) {
      v.push_back(prime[i]);
    }
  }
  dp[0] = 1;
  for(ll i = a; i <= b; ++i) {
    int res = 0;
    for(int j = 0; j < int(v.size()); ++j) {
      if(i % v[j] == 0) {
        res |= (1 << j);
      }
    }
    for(int j = (1 << int(v.size())) - 1; j >= 0; --j) {
      if(!(j & res)) {
        dp[j | res] += dp[j];
      }
    }
  }
  for(int i = 0; i < (1 << int(v.size())); ++i) {
    ans += dp[i];
  }
  cout << ans - 1;
  return 0;
}

相關文章