2024.11.15 CW 模拟赛

题面

T1

诈骗题, 考虑贪心.

容易发现一个合法的方阵每一行或者每一列一定形如 "ABAB..." 或者 "BABA...".

那么可以对横行和纵列分别进行贪心.

最后取 \(\max\) 即可.

时间复杂度 \(\mathcal{O}(n^2)\).

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
#include "iostream" using namespace std; template <typename T> inline void read(T &x) { x = 0; char ch = getchar(); while (!isdigit(ch)) ch = getchar(); while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); return; } const int N = 1e3 + 1; short n, a[N][N]; int ans1 = 0, ans2 = 0; inline void init() { read(n); for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) read(a[i][j]); int tmp1 = 0, tmp2 = 0; for (int j = 1; j <= n; j += 2) tmp1 += a[i][j]; for (int j = 2; j <= n; j += 2) tmp2 += a[i][j]; ans1 += max(tmp1, tmp2); } return; } inline void calculate() { for (int j = 1; j <= n; ++j) { int tmp1 = 0, tmp2 = 0; for (int i = 1; i <= n; i += 2) tmp1 += a[i][j]; for (int i = 2; i <= n; i += 2) tmp2 += a[i][j]; ans2 += max(tmp1, tmp2); } cout << max(ans1, ans2) << '\n'; return; } inline void solve() { init(); calculate(); return; } int main() { solve(); return 0; }

T2

动态规划.
首先可以跑一遍 \(Floyd\) 计算最短路, 即用按键 \(j\) 替代按键 \(i\) 的最小天数.

接下来定义状态.

\(f_i\) 为到前 \(i\) 位是合法序列的最小花费.

那么有转移方程:

\[f_i=\min(f_j+cost[j,i,l]),\ 1 \le j \le i-k \]

其中 \(cost[j,i,l]\) 表示将 \(j\)\(i\) 全部变成按键 \(l\) 的花费.

这个可以使用前缀和进行优化.

届时时间复杂度为 \(\mathcal{O}(n^2 m^2)\), 不足以通过此题.

可以发现瓶颈是在枚举 \(f_j+cost[j,i,l]\).

因其具有单调性, 我们记 \(pre_c=dp_{i-k}-sum_{i-k,j},\ 1 \le c \le m\)

所以方程可以优化为:

\[dp_i=\min(pre_c+sum_{i,c}),\ 1 \le c \le m \]

时间复杂度 \(\mathcal{O}(nm)\).

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
#include "iostream" #include "cstring" using namespace std; const int M = 27, N = 1e5 + 10; int n, m, k; int f[M][M]; int pos[N]; long long sum[N][M]; string s; inline void init() { cin >> n >> m >> k; cin >> s, s = " " + s; for (int i = 1; i <= m; ++i) for (int j = 1; j <= m; ++j) cin >> f[i][j]; for (int K = 1; K <= m; ++K) for (int i = 1; i <= m; ++i) for (int j = 1; j <= m; ++j) f[i][j] = min(f[i][K] + f[K][j], f[i][j]); for (int i = 1; i <= n; ++i) { pos[i] = s[i] - 'a' + 1; for (int j = 1; j <= m; ++j) sum[i][j] = sum[i - 1][j] + f[pos[i]][j]; } return; } long long dp[N], pre[N]; inline void calculate() { memset(dp, 63, sizeof dp); dp[0] = 0; memset(pre, 63, sizeof pre); for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { if (i >= k) pre[j] = min(pre[j], dp[i - k] - sum[i - k][j]); dp[i] = min(dp[i], pre[j] + sum[i][j]); } } cout << dp[n] << '\n'; return; } inline void solve() { init(); calculate(); return; } int main() { cin.tie(nullptr)->ios::sync_with_stdio(false); solve(); return 0; }

T3

动态规划, 质因数分解

阅读题面, 容易发现原题意可以转化成:

当前有 \(cnt\) 个环, 且 \(\sum_{i=1}^{cnt} len_i=n\), 求 \(\sum \rm{lcm} (len_i)\).

而对于每一种情况, \(\rm{lcm}(len_i)\) 的值只与每个环长度的质因数有关, 是每一个质因数取最高次幂乘起来.
所以我们可以考虑枚举素数进行 dp.

\(f_{i,j}\) 表示当前所有长度不为 \(1\) 的环总长度为 \(i\), 每个环长度中最大的质因子不超过 \(p_j\) 的答案.

考虑枚举 \(p_j\) 的次幂作为新的环的长度(因为如果加到原来的环之后算答案回去重所以是一样的),
故有以下转移方程:

\[f_{i,j}=f_{i,j-1}+ \sum_{p_j^k \le i} p^k_j f_{i-p_j^k,j-1} \]

可以 \(\mathcal{O}(n^2)\) 枚举.
注意 \(i\) 需要倒序枚举(背包).

另外附一个结论:
如果 \(p\) 是一个素数集(包括 1), \(c\) 是一个自然数集, \(\lvert c \rvert = \lvert p \rvert = m\), 那么

\[\forall i \in [1,m],\ len_i=p_i^{c_i} \]

这一类的拆分, 囊括了所有的答案.

  • \(Proof\)
    假设存在一种划分的 \(len_j\) 不符合上述条件, 记 \(len_j\)\(k\) 个质因数.
    那么可以把 \(len_j\) 拆成 \(k\) 个符合上述条件的环, 对 \(\rm{lcm}\) 没有影响.
    并且可以证明拆出来的 \(k\) 个环的总长度不大于 \(len_j\) (类比 \(a,b \ge 2 \rightarrow ab \ge a+b\)).
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
#include "iostream" #include "bitset" using namespace std; const int N = 1e4 + 1, M = 4e3 + 1; short cnt = 0, p[M]; bitset<N> vis; inline void calc_prime(int n) { for (short i = 2; i <= n; ++i) { if (!vis[i]) p[++cnt] = i; for (short j = 1; i * p[j] <= n; ++j) { vis[i * p[j]] = 1; if (!(i % p[j])) break; } } return; } short n; int mod; inline void init() { cin >> n >> mod; calc_prime(n); return; } int f[N]; inline void calculate() { f[0] = 1; for (int i = 1; i <= cnt; ++i) for (int j = n; j >= 1; --j) for (int k = p[i]; k <= j; k *= p[i]) f[j] = (f[j] + 1ll * f[j - k] * k % mod) % mod; int ans = 0; for (int i = 1; i <= n; ++i) ans = (ans + f[i]) % mod; cout << ans + 1 << '\n'; return; } inline void solve() { init(); calculate(); return; } int main() { solve(); return 0; }
posted @   Steven1013  阅读(6)  评论(0编辑  收藏  举报
(评论功能已被禁用)
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开