2025 年 4 月 29 日 模拟赛题解
一、换防

不难发现,这些人和要去的位置形成了很多置换环。一开始只有 \(1\) 或者 \(n + 1\) 能动(以下将第 \(x\) 支军队简记为 \(x\))
如果某个置换环内有一个岗哨内有一个多余的军队,那么就可以让这个环里的人快速换完后再让这支多余的军队离开。
所以一开始只有 \(1\) 所在的置换环有一个军队,也就是说,\(1\) 所在的置换环被 "解锁" 了。
我们要派人去解锁其它的置换环。
有两种情况:
第一种,就是环与环在数轴上相交了。
例如说,\(w\) 数组是:3 4 5 2 1
所以 \(1\) 可以在去第 \(3\) 个岗哨的途中到达第 \(2\) 个岗哨,然后就可以解锁 \(2\), \(4\) 形成的置换环。等 \(2\) 与 \(4\) 交换位置之后再让 \(1\) 到第 \(3\) 个岗哨去。
我们在解决 \(1\), \(3\), \(5\) 形成的置换环时直接解锁了 \(2\), \(4\) 形成的置换,没有付出任何多余代价。
第二种,就是环与环在数轴上不相交
例如说,\(w\) 数组是:2 3 1 5 4
一开始 \(1\), \(2\), \(3\) 形成的置换环被解锁了,但是它们在置换的途中并没有碰到 \(4\), \(5\) 形成的置换环。
所以就得派人去解锁。我们让 \(2\) 到达第 \(3\) 个岗哨后,让 \(3\) 去第 \(4\) 个岗哨解锁 \(4\), \(5\) 形成的置换环,回到第 \(3\) 个岗哨,再继续 \(1\), \(2\), \(3\) 的置换流程。
为了解锁 \(4\), \(5\) 形成的置换环,第 \(3\) 个岗哨到第 \(4\) 个岗哨的路被走了两次。
所以,我们可以将所有相交的环合并,然后计算所有不相交的环之间的距离再乘 2,最后加上所有置换环内的置换流程需要走的路即可。
细节:
1.如果这个置换环是自环,那么直接删掉,否则会影响计算。
2.如果 \(1\) 所在的置换环是自环,那么不需要删掉,因为 \(1\) 必须去别的地方解锁其它环。
Code:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 10005;
const LL INF = 1e15 + 5;
struct Node
{
LL L, R;
} g[MAXN];
LL n, Pos[MAXN], to[MAXN], cnt = 0, anss = 0, diff[MAXN], rig;
bool vis[MAXN];
LL myabs(LL x)
{
if(x < 0) return -x;
else return x;
}
void DFS(LL u)
{
vis[u] = true; g[cnt].L = min(g[cnt].L, u); g[cnt].R = max(g[cnt].R, u);
if(!vis[to[u]]) DFS(to[u]);
return;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
for(LL i = 2; i <= n; i ++) cin >> Pos[i];
for(LL i = 1; i <= n; i ++)
{
cin >> to[i];
anss += myabs(Pos[i] - Pos[to[i]]);
}
for(LL i = 1; i <= n; i ++)
{
if(vis[i]) continue;
cnt ++; g[cnt].L = INF; g[cnt].R = -INF; DFS(i);
diff[g[cnt].L] ++; diff[g[cnt].R] --;
}
for(LL i = 1; i <= n; i ++) diff[i] += diff[i - 1];
rig = 0;
for(LL i = n - 1; i >= 1; i --)
{
if(diff[i] == 0) continue;
rig = i; break;
}
for(LL i = 1; i <= rig; i ++) if(diff[i] == 0) anss += (Pos[i + 1] - Pos[i]) * 2;
cout << anss << '\n';
return 0;
}
二、计数
题意:给定两个整数 \(n\), \(m\),求在 \(1\) ~ \(n\) 中,一共有多少个数的数位和是 \(m\) 的倍数(\(1 \leq n \leq 10^{1000}\),\(1 \leq m \leq 1000\))。
很明显,这是数位 DP 板子。
我们设 \(n\) 有 \(k\) 位。
尝试设计状态:\(f_{i, j, 0/1}\) 表示当前到了第 \(i\) 位,前 \(i\) 的数位和模 \(m\) 余 \(j\),当前数的前 \(i\) 位 是否与 \(n\) 的前 \(i\) 位不同,有多少个数。
转移:
如果 当前数的前 \(i - 1\) 位 与 \(n\) 的前 \(i - 1\) 位 相等,那么说明这个数的第 \(i\) 位一定要小于等于 \(n\) 的第 \(i\) 位。否则第 \(i\) 位就可以是任意值。转移显然,这里就不写了。
初值:\(f_{0, 0, 1} = 1\),其余的 \(f_{0, x, x}\) 都是 \(0\)。
答案:\(f_{k, 0, 0} + f_{k, 0, 1}\)
设计完这些后直接用记忆化搜索跑一遍就完事了。
细节:
1.\(f_{k, 0, 1}\) 不要算,直接看 \(n\) 的数位和是不是 \(m\) 的倍数就行了。
2.注意有 \(0\) 的出现,答案要减 \(1\)。
3.\(m\) 过大且位数小的时候是无解的,最好加上剪枝跳过这种情况,这样可以省很多时间。
Code:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 123456789;
string n;
int m, k, a[1005], dp[1005][1005][2];
int dfs(int step, int sum, bool f)
{
if (step < 1) return (sum == 0) & f;
if (step * 9 < sum) return 0;
if (dp[step][sum][f]) return dp[step][sum][f];
int res = 0;
for (int i = 0; i <= (f ? 9 : a[step]); i++)
{
res += dfs(step - 1, ((sum - i) % m + m) % m, f | (i != a[step]));
res %= mod;
}
return dp[step][sum][f] = res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
int num = 0;
k = n.size();
n = ' ' + n;
for (int i = 1; i < n.size(); i++) a[i] = n[i] - '0', num += a[i];
for (int i = 1, j = k; i <= j; i++, j--) swap(a[i], a[j]);
cout << ((dfs(k, 0, 0) - 1 + (num % m == 0)) % mod + mod) % mod << "\n";
return 0;
}
三、森林


同学说暴力就能 AC,我不想写了……
四、见证者


由于我太菜了,现在只会这道题的 \(40\) 分做法。
注意到 \(nm \leq 200\),说明 \(\min(n, m) \leq 14\)(这是一个 Trick),考虑状压 dp。
设 \(f_{i, s}\) 表示第 \(i\) 行,状态为 \(s\) 的方案数,\(s\) 是一个二进制数,其中 \(s_i = 0\) 表示这个格子已经被用过了,\(s_i = 1\) 表示这个格子是竖着的,占用了第 \(i + 1\) 行的格子。
转移很简单,这里不赘述了。
需要注意的是,如果硬存空间是不够的,所以开滚动数组或者想办法直接把第一维压掉即可。
Code:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, m, f[(1 << 14) + 5];
const int mod = 998244353;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
if (n > m) swap(n, m);
f[0] = 1;
for (int i = 1; i <= m; i++)
{
for (int s = 0; s < (1 << n); s++)
if ((s & 1) == 1) swap(f[s], f[((1 << n) - 1) ^ s]);
for (int j = 0; j < n - 1; j++)
{
for (int s = 0; s < (1 << n); s++)
{
if ((s & (1 << j)) && (s & (1 << j + 1)))
{
f[(s ^ (1 << j) ^ (1 << j + 1))] += f[s];
f[(s ^ (1 << j) ^ (1 << j + 1))] %= mod;
}
}
}
}
cout << f[0] << "\n";
return 0;
}

浙公网安备 33010602011771号