luogu P4296 [AHOI2007] 密码箱 [数论] [推柿子]

该博客探讨了一道编程竞赛题目,涉及寻找所有小于给定数n的正整数x,满足x^2 ≡ 1 (mod n)的条件。通过平方差公式和因数分解,提出了一种枚举n的约数并判断解的算法,重点在于优化枚举过程,避免无效计算,提高效率。博主分享了完整的C++代码实现,并指出特殊情况的处理方法。

P4296 [AHOI2007] 密码箱 link

在这里插入图片描述

题目大意:

给定 n n n,求出所有正整数 x x x,满足小于 n n n

x 2 ≡ 1 ( m o d    n ) x^2\equiv1\left(mod\;n\right) x21(modn) 将满足的 x x x 按从小到大的顺序依次换行输出


思路:

x 2 = k n + 1 x^2=kn+1 x2=kn+1

移项发现是个平方差

( x + 1 ) ( x − 1 ) = k n (x+1)(x-1)=kn (x+1)(x1)=kn

考 虑 拆 解 k , n k,n k,n

( x + 1 ) = k 1 ∗ n 1 (x+1)=k_1*n_1 (x+1)=k1n1

( x − 1 ) = k 2 ∗ n 2 (x-1)=k_2*n_2 (x1)=k2n2

k 1 ∗ k 2 = k , n 1 ∗ n 2 = n k_1*k_2=k,n_1*n_2=n k1k2=k,n1n2=n

发现可以枚举 n n n 的约数 y y y

记作其中的 n 1 n_1 n1

则有 n 2 = n / y n_2=n/y n2=n/y, ( x + 1 ) = k 1 ∗ y (x+1)=k1*y (x+1)=k1y

因为 x < n x<n x<n, x + 1 < = n x+1<=n x+1<=n,考虑枚举倍数 k 1 k_1 k1

假设当前枚举倍数为 d d d

则有 ( x + 1 ) = d ∗ y (x+1)=d*y (x+1)=dy

x = d ∗ y − 1 x=d*y-1 x=dy1

④⑤带入②,则有 ( d ∗ y − 2 ) = k 2 ∗ ( n / y ) (d*y-2)=k_2*(n/y) (dy2)=k2(n/y)

( d ∗ y − 2 ) (d*y-2) (dy2) ( n / y ) (n/y) (n/y) 的倍数,则 x = d ∗ y − 1 x=d*y-1 x=dy1 为一组符合的解

同理可以将 y y y 记作其中的 n 2 n_2 n2,即由②推①

则有 ( d ∗ y + 2 ) = k 1 ∗ ( n / y ) (d*y+2)=k1*(n/y) (dy+2)=k1(n/y)

( d ∗ y + 2 ) (d*y+2) (dy+2) ( n / y ) (n/y) (n/y) 的倍数,则 x = d ∗ y + 1 x=d*y+1 x=dy+1 为一组可能的解,

因为这里是 ( x − 1 ) − > ( x + 1 ) (x-1)->(x+1) (x1)>(x+1) x x x 可能大于 n n n

ps:

  1. 一组约数 y y y , n / y n/y n/y,可能对应两个解
    y − > x − 1 , ( n / y ) − > x + 1 y->x-1,(n/y)->x+1 y>x1,(n/y)>x+1 或者 y − > x + 1 , ( n / y ) − > x − 1 y->x+1,(n/y)->x-1 y>x+1,(n/y)>x1
  2. 枚举倍数时,选择大于 n \sqrt{n} n 的,
    不然 ∑ ( k i ) \sum(k_i) (ki) 可能和会很大
    然后对于每个枚举的 约数,
    可以 ( x − 1 ) − > ( x + 1 ) (x-1)->(x+1) (x1)>(x+1) 或者 ( x + 1 ) − > ( x − 1 ) (x+1)->(x-1) (x+1)>(x1)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <cmath>

using namespace std;

typedef long long ll;

const int N = 1e6 + 10;
ll n, ans[N], num, cnt; 
  
void Jud(ll y) 
{
	for (ll d = 0; d <= n / y; d++) 
	{
		num = (d * y - 2);
		if (!(num % (n / y))) ans[++cnt] = d * y - 1;		
		num = (d * y + 2);
		if (!(num % (n / y))) ans[++cnt] = d * y + 1;
	}
}

int main() {
	scanf("%lld", &n);
	if (n == 1) { printf("None\n"); return 0; }
	ll siz = sqrt(n);
	for(ll i = 1; i <= siz; i++) if (!(n % i)) Jud(n / i);
	sort(ans + 1, ans + cnt + 1);
	int m = unique(ans + 1, ans + cnt + 1) - ans - 1;
	int l = 1, r = m;
	while (ans[r] >= n) r--;
	while (ans[l] < 0) l++; 
	if (!(r - l + 1)) printf("None");
	else for(int i = l; i <= r; i++) printf("%lld\n", ans[i]);
}

### 题目解析 洛谷 P1645 序列问题描述为给定若干个区间,每个区间有对应的整数数量要求,需要在满足这些区间内整数数量要求的情况下,求出最少需要选择多少个整数。该问题可以使用差分约束系统来解决。差分约束系统是一种特殊的不等式组,通过将问题转化为图的最短路问题来求解。对于每个区间 `[x, y]` 要求至少有 `z` 个整数,可转化为不等式 `s[y] - s[x - 1] >= z`,其中 `s[i]` 表示从 `1` 到 `i` 选择的整数数量。同时,还有隐含条件 `0 <= s[i] - s[i - 1] <= 1`,表示每个位置最多选一个整数,最少不选。 ### 解题思路 1. **构建图**:将不等式转化为图的边。对于 `s[y] - s[x - 1] >= z`,可变形为 `s[x - 1] <= s[y] - z`,对应图中从 `y` 到 `x - 1` 有一条权值为 `-z` 的边;对于 `s[i] - s[i - 1] >= 0` 变形为 `s[i - 1] <= s[i]`,对应图中从 `i` 到 `i - 1` 有一条权值为 `0` 的边;对于 `s[i] - s[i - 1] <= 1` 变形为 `s[i] <= s[i - 1] + 1`,对应图中从 `i - 1` 到 `i` 有一条权值为 `1` 的边。 2. **求解最短路**:使用最短路算法(如 SPFA 算法)求解图的最短路。从一个源点开始,不断更新各点的最短距离。 3. **得出结果**:最终 `s[最大位置]` 即为满足所有区间要求的最少整数数量。 ### 代码实现 ```cpp #include <cstdio> #include <iostream> using namespace std; const int maxn = 100010; int head[maxn], nnext[maxn], to[maxn], team[maxn], length[maxn]; int n; int tot, s = 0, t = 0; int dis[maxn]; bool b[maxn]; void add(int x, int y, int l) { tot++; nnext[tot] = head[x]; head[x] = tot; to[tot] = y; length[tot] = l; } int main() { scanf("%d", &n); for (int i = 0; i <= 1000; i++) { add(i, i - 1, -1); add(i - 1, i, 0); } for (int i = 1; i <= n; i++) { int x, y, z; cin >> x >> y >> z; add(x - 1, y, z); } for (int i = 0; i <= 1000; i++) { dis[i] = -1e9; } dis[0] = 0; team[t] = 0; t++; b[0] = true; while (s != t) { int now = team[s]; s++; b[now] = false; for (int i = head[now]; i; i = nnext[i]) { int y = to[i]; if (dis[y] < dis[now] + length[i]) { dis[y] = dis[now] + length[i]; if (!b[y]) { team[t] = y; t++; b[y] = true; } } } } cout << dis[1000]; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值