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_{i\mid n}\sum_{j=1}^{\tfrac ni} \frac{\varphi(i)}{ij}\binom{\tfrac{n}{i}+j-1}{2j-1} \]

考虑后半部分,对其进行变换:

\[\begin{aligned} &=\sum_{j=1}^{\tfrac ni} \frac{\varphi(i)}{ij}\binom{\tfrac{n}{i}+j-1}{2j-1}\\ &=\frac{\varphi(i)}{n}\sum_{j=1}^{\tfrac ni} \frac{n}{ij}\binom{\tfrac{n}{i}+j-1}{2j-1}\\ &=\frac{\varphi(i)}{n}\sum_{j=1}^{\tfrac ni} (\frac{\tfrac{n}i+j}{j}-1)\binom{\tfrac{n}{i}+j-1}{2j-1}\\ &=\frac{\varphi(i)}{n}\sum_{j=1}^{\tfrac ni} 2\binom{\tfrac{n}{i}+j}{2j}-\binom{\tfrac{n}{i}+j-1}{2j-1}\\ \end{aligned} \]

注意到后面的和式 \(\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\),那么这个概率显然是:

\[\dfrac{1}{c+1}+\dfrac{1}{c+1}\times \sum p \times \dfrac{1}{c+1}+\dfrac{1}{c+1}\times \sum p \times \dfrac{1}{c+1}\times \sum p \times\dfrac{1}{c+1}+\cdots \]

求和后发现 \(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;
}
posted @ 2025-02-25 21:00  UKE_Automation  阅读(49)  评论(2)    收藏  举报