title: 数论问题
tags: [数论]
categories: [OI, 算法专题]

P12237 蓝桥杯 2023 国 Java A 质数排序

原题 · 标签:#数论

考点

质数筛+计数技巧

题意

  • 定义"质数排序":将序列中下标为质数的位置上的数按升序排序,其余位置不变。
  • 对 \(1 \sim n\) 的所有排列分别进行质数排序,求所有结果序列的逆序对数量之和,对 \(998244353\) 取模。

数据范围:\(1 \le n \le 10^6\)。

思路

对质数位置进行升序排列后,质数位置之间不可能有逆序对。所以我们讨论质数位置与非质数位置间,非质数位置与非质数位置之间的答案。

设质数位置数量是 \(p\),非质数位置 \((n-p)\) 为 \(c\)。

由于是排列,所以对于在位置 \(i,j\) 填的两个数 \(a,b\),\((a,b)\) 和 \((b,a)\) 都会算到。所以这样一个数对的逆序对期望是 \(\frac{1}{2}\)。由此易算出非质数位置之间的答案数量为 \(\dfrac{n! \times c \times (c-1)}{4}\)。

非质数位置落在每个质数区间的概率是 \(\frac{1}{p+1}\),其前面质数数量与在质数中排位的绝对值就是其贡献。这个是两个等差数列的形式。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
const int MOD = 998244353;
const int N = 1e6+10;
int qpow(int x,int p){
	int tmp = x;
	int res = 1;
	while (p){
		if (p & 1) res = res * tmp % MOD;
		tmp = tmp * tmp % MOD;
		p /= 2;
	}
	return res;
}
bool notPrime[N];
vector <int> p;
void prime(int n){
	notPrime[1] = 1;
	for (int i=2;i<=n;i++){
		if (!notPrime[i]) p.push_back(i);
		for (int u:p){
			if (u * i > n) break;
			notPrime[u * i] = 1;
			if (i % u == 0) break;
		}
	}
}
int n;
int inv2,inv4,invp1;
int fac[N];
void init(){
	prime(n);
	inv2 = qpow(2,MOD-2),inv4 = qpow(4,MOD-2),invp1 = qpow(p.size()+1,MOD-2);
	fac[0] = 1;
	for (int i=1;i<=n;i++){
		fac[i] = fac[i-1] * i % MOD;
	}
}
int ans;
void solve(){
	int c = n - p.size();
//	Dl(c);
	ans += fac[n] * c % MOD * (c - 1) % MOD * inv4 % MOD;
	int k = 0;
	for (int i=1;i<=n;i++){
		if (!notPrime[i]){
			k++;
			continue;
		}
		ans += fac[n] * invp1 % MOD * (k * (k + 1) % MOD * inv2 % MOD + (p.size() - k) * (p.size() - k + 1) % MOD * inv2 % MOD) % MOD;
		ans %= MOD;
	}
}
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin >> n;
	init();
	solve();
	cout << ans;
	return 0;
}

ABC420G sqrt(n²+n+X)

原题

考点

初中数学级别的推式子

思路

转化式子为:

\[\begin{aligned} n^2 + n + x &= k^2 \\ n^2 + n - k^2 &= -x \\ \left(n + \frac{1}{2}\right)^2 - \frac{1}{4} - k^2 &= -x \\ (n + k + \tfrac{1}{2})(n - k + \tfrac{1}{2}) &= -x + \frac{1}{4} \\ (2n + 2k + 1)(2n - 2k + 1) &= -4x + 1 \end{aligned} \]

对 \(-4x + 1\) 做分解,求出 \(n\),记得判断是不是解。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define f(i,a,b) for(int i=(a);i<=(b);i++)
#define Dl(a) cout << #a << " : " << a << "\n";
#define D(a) cout << #a << " : " << a;
#define Da(a,i,j) cout << #a << " : ";f(idx,i,j){cout << a[idx] <<" ";}
int x;
set <int> ans;
void calc(int d1,int d2){
//	if (d2 < d1) swap(d1,d2);
	if (d1 % 4 == d2 % 4) return;
	if ((d2 - d1 - 2) % 4 != 0) return;
	int n = (d2 - d1 - 2) / 4;
	__int128 check = __int128(n) * n + x + n;
	if (check < 0) return;
	ans.insert(n);
}
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin >> x;
	int y = 4 * x - 1;
	for (int d1=1;d1*d1 <= llabs(y);d1++){
		if (y % d1) continue;
		int d2 = y / d1;
		calc(d1,d2);
		calc(d2,d1);
		calc(d1 * -1,d2 * -1);
		calc(d2*-1,d1*-1);
	}
	cout << ans.size() << "\n";
	for (int u:ans){
		cout << u << " ";
	}
	return 0;
}