2025 省选模拟 13
2025 省选模拟 13
得分
| T1 | T2 | T3 | 总分 | 排名 |
|---|---|---|---|---|
| \(100\) | \(10\) | \(10\) | \(120\) | \(1/7\) |
题解
T1 数
简单题。根据初中所学知识,平面直角坐标系上的等腰直角三角形有一个经典处理方法:构造三垂直。考虑构造出来的两个直角三角形的直角边长 \(a,b\),枚举 \(a+b\),则 \(a\) 取值在一定范围内,可以简单求出所有方案数。注意这样算的时候要注意一下 \(a=0\) 或 \(b=0\) 时的实现。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int Maxn = 2e5 + 5;
const int Inf = 2e9;
const int Mod = 20120712;
int T;
int calc(int n, int m) {
int ans = 0;
for(int i = 1; i <= n; i++) {
int l = (i >> 1) + 1, r = min(i, m), sum = 0;
if(l <= r) sum = ((r - l + 1) * m % Mod - ((l + r) * (r - l + 1) / 2) % Mod + (r - l + 1) + Mod) % Mod * 2 % Mod;
if(!(i & 1) && (i >> 1) <= m) sum = (sum + m - (i >> 1) + 1) % Mod;
ans = (ans + (n - i + 1) * sum % Mod) % Mod;
}
return ans;
}
int n, m;
void solve() {
cin >> n >> m;
int res = calc(n, m) + calc(m, n);
int lst = 0;
for(int i = 1; i <= min(n, m); i++) {
lst = (lst + (n - i + 1) * (m - i + 1) % Mod) % Mod;
}
lst = lst * 4 % Mod;
res = (res * 2 % Mod - lst + Mod) % Mod;
cout << res << '\n';
}
signed main() {
freopen("count.in", "r", stdin);
freopen("count.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T;
while(T--) solve();
return 0;
}
T2 树
首先手玩不难发现:最后的树的形态大概是将环劈成若干个链,每条链上有一个节点连向中心点。所以设每个链长为 \(a_i\),则我们要求所有满足 \(\sum a_i =n\) 的 \(\prod a_i\) 之和。
不过很显然这中间会有重复的情况,此时我们就想到使用 Burnside 引理进行去重。对于长为 \(k\) 的序列,如果它是一个置换 “旋转 \(p\) 次” 的不动点的话,那么其应该有长度为 \(\gcd(k,p)\) 的循环节。考虑设循环节长度为 \(j\),个数为 \(i\),则 \(k=ij\)。那么这个序列是不动点的条件就是 \(\gcd(ij,p)=j\)。不难发现合法的 \(p\) 有 \(\varphi(i)\) 个,于是每个这样的序列乘上的系数就是 \(\tfrac{\varphi(i)}{ij}\)。
现在考虑计算循环节内部的积,考虑组合意义,相当于我们共有 \(\tfrac{n}{i}\) 个元素,划分成 \(j\) 个集合,并且每个集合选一个数的方案数。隔板法知答案为 \(\binom{\tfrac{n}{i}+j-1}{2j-1}\)。所以答案为:
考虑后半部分,对其进行变换:
注意到后面的和式 \(\sum\limits_{j=1}^i 2\binom{i+j}{2j}-\binom{i+j-1}{2j-1}\) 可以写成 \(f_{2i}+f_{2i-2}-2\) 的形式,其中 \(f\) 是斐波那契数列,满足 \(f_0=f_1=1,f_i=f_{i-1}+f_{i-2}\),证明考虑直接利用通项公式。不难想到用矩阵去优化转移,这样复杂度下降到 \(O(T(\sqrt{n}+\sqrt[3]{n}\log n))\)。
然而题目不保证 \(n,m\) 互质,我们无法利用逆元求解。有一个经典的思路是上面的过程统一对 \(nm\) 取模,最后对答案除以 \(n\) 即可。显然这样做是正确的。
#include <bits/stdc++.h>
#define ll __int128
using namespace std;
typedef long long LL;
const int Maxn = 2e5 + 5;
const int Inf = 2e9;
int T;
LL n, Mod;
vector <int> fac, pr;
LL mat[31][2][2];
void solve() {
cin >> n >> Mod;
Mod *= n;
fac.clear(), pr.clear();
int tmp = n;
for(int i = 2; i * i <= tmp; i++) {
if(tmp / i * i == tmp) {
pr.push_back(i);
while(tmp / i * i == tmp) tmp /= i;
}
}
if(tmp > 1) pr.push_back(tmp);
for(int i = 1, p; i <= (p = n / i); i++) {
if(i * p == n) {
fac.push_back(i);
if(i != p) fac.push_back(p);
}
}
for(int i = 1; i <= 30; i++) {
mat[i][0][0] = ((ll)mat[i - 1][0][0] * mat[i - 1][0][0] + (ll)mat[i - 1][0][1] * mat[i - 1][1][0]) % Mod;
mat[i][0][1] = ((ll)mat[i - 1][0][0] * mat[i - 1][0][1] + (ll)mat[i - 1][0][1] * mat[i - 1][1][1]) % Mod;
mat[i][1][0] = ((ll)mat[i - 1][1][0] * mat[i - 1][0][0] + (ll)mat[i - 1][1][1] * mat[i - 1][1][0]) % Mod;
mat[i][1][1] = ((ll)mat[i - 1][1][0] * mat[i - 1][0][1] + (ll)mat[i - 1][1][1] * mat[i - 1][1][1]) % Mod;
}
LL ans = 0;
for(auto i : fac) {
int p = n / i, phi = p;
for(auto j : pr) if(p % j == 0) phi = phi / j * (j - 1);
LL f[2] = {1, 1};
int tim = (i << 1) - 2;
for(int i = 0; tim; i++, tim >>= 1) {
if(tim & 1) {
LL num1 = ((ll)f[0] * mat[i][0][0] + (ll)f[1] * mat[i][0][1]) % Mod;
LL num2 = ((ll)f[0] * mat[i][1][0] + (ll)f[1] * mat[i][1][1]) % Mod;
f[0] = num1, f[1] = num2;
}
}
ans = (ans + (ll)phi * (f[0] + f[1] + f[1] - 2)) % Mod;
}
cout << ans / n << '\n';
}
signed main() {
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T;
mat[0][0][0] = mat[0][0][1] = mat[0][1][0] = 1;
mat[0][1][1] = 0;
while(T--) solve();
return 0;
}
T3 书
显然我们可以枚举栈中元素的状态,然后高斯消元即可解出答案。显然所有状态之间的转移构成了一个树形结构,所以可以直接做一遍树上随机游走求出答案。不过这些做法复杂度都过高。
关键点在于所有状态构成树形结构,并且当中有状态是重复的。考虑怎样的状态是等价的,显然当栈顶大小和厚度和相等的时候状态是本质相同的。考虑设 \(p(i,j)\) 表示当栈顶大小为 \(i\),厚度和为 \(j\),下一次回到 \((i,j)\) 且操作是弹栈(即走到父亲)的概率。令当前状态所有儿子的 \(1-p\) 之和为 \(P\),那么这个概率显然是:
求和后发现 \(p(i,j)=\tfrac{1}{c+1-\sum p}=\tfrac{1}{1+P}\)。然后设 \(f(i,j)\) 表示当栈顶大小为 \(i\),厚度和为 \(j\),期望走多少步 \(b>w\) 或者走回父亲。前者实际上就是从儿子走死的期望步数。考虑 \((i,j)\) 所有儿子的 \(f\) 之和为 \(F\),显然经过一轮共期望 \(1+\tfrac{F}{c+1}\) 步后,我们会有 \(\tfrac{P+1}{c+1}\) 的概率满足上述要求。那么根据经典结论我们只需要进行期望 \(\tfrac{c+1}{P+1}\) 轮后就可以满足要求,那么总期望步数就是 \(\tfrac{c+1+F}{P+1}\)。于是 \(f(i,j)=\tfrac{c+1+F}{P+1}\)。
实现的时候可以考虑记忆化搜索,这样更符合树形结构的转移。复杂度 \(O(Tn^2 w)\)。
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 2e5 + 5;
const int Inf = 2e9;
int T, n, w;
struct node {
int a, b;
}a[Maxn];
double p[205][205], f[205][205];
bool vis[205][205];
void dfs(int x, int y) {//大小为 x,厚度和为 y
if(y > w) {p[x][y] = f[x][y] = 0; return ;}
if(x == a[1].a) {p[x][y] = f[x][y] = 1; return ;}
if(vis[x][y]) return ;
double P = 0, F = 0;
int tot = 0;
for(int i = 1; i <= n; i++) {
if(a[i].a >= x) break;
dfs(a[i].a, y + a[i].b);
tot++;
P += 1 - p[a[i].a][y + a[i].b];
F += f[a[i].a][y + a[i].b];
}
p[x][y] = 1.0 / (1 + P);
f[x][y] = (tot + 1 + F) * 1.0 / (1 + P);
vis[x][y] = 1;
return ;
}
void solve() {
cin >> n >> w;
for(int i = 1; i <= n; i++) cin >> a[i].a >> a[i].b;
sort(a + 1, a + n + 1, [&](node x, node y){return x.a < y.a;});
dfs(101, 0);
if(f[101][0] > 18000) cout << "INF\n";
else cout << fixed << setprecision(3) << f[101][0] << '\n';
for(int i = 0; i <= 101; i++) for(int j = 0; j <= 201; j++) vis[i][j] = f[i][j] = p[i][j] = 0;
}
int main() {
freopen("book.in", "r", stdin);
freopen("book.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> T;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号