视频加载失败

安吉D19 T1

1652 字
8 分钟
安吉D19 T1
原题呈现

排列数#

题目描述#

排列数的符号为 AjiA_j^i,意为从 jj 个物品中任选 ii 个,并按照任意顺序排成一列的方案数.和组合数不同,排列数与选出物品的顺序有关.

以琪露诺的智商当然学不明白排列数的含义,所以她把公式背了下来:

Aji=j!(ji)!A_j^i = \frac{j!}{(j-i)!}

幸运的是,期末考试的压轴大题刚好考了排列数.题目是这样的: 求有多少对 (i,j)(i, j),满足 1ijn1 \le i \le j \le nAjiA_j^ikk 的倍数.

可惜只背公式当然是没什么用的,琪露诺完全不会这个题目,你能帮帮她吗?

输入格式#

输入仅一行,包含两个整数 n,kn, k,表示排列数的大小限制与倍数限制.

输出格式#

输出仅一个整数,表示满足 1ijn1 \le i \le j \le nAjiA_j^ikk 的倍数的有序数对 {i,j}\{i, j\} 的数量.

样例#

输入 #1#

10 1

输出 #1#

55

输入 #2#

10 6

输出 #2#

43

输入 #3#

100 66

输出 #3#

4540

样例解释#

  • 样例一中,满足 1ij101 \le i \le j \le 10 的任意 AjiA_j^i 都是 11 的倍数,共 (10+12)=55\binom{10+1}{2}=55 个.

  • 样例二中,符合范围条件但是66 的倍数的排列数有:

A11, A21, A22, A31, A41, A51, A52, A71, A81, A82, A91, A101 A_1^1,\ A_2^1,\ A_2^2,\ A_3^1,\ A_4^1,\ A_5^1,\ A_5^2,\ A_7^1,\ A_8^1,\ A_8^2,\ A_9^1,\ A_{10}^1

1212 个. 因此符合条件的为 5512=4355 - 12 = 43 个.

数据范围#

数据包分数nn \lekk \le特殊性质
1301010010^{100}1010
21030030010610^6
3103000300010610^6
41010610^622
51010610^6100100
61010610^610910^9A
72010610^610910^9

特殊性质 A:保证 kk 为质数.

对于所有数据

  • 1n1061 \le n \le 10^6
  • 1k1091 \le k \le 10^9

我们可以将排列数的公式变一下形,为:

Aji=(ji+1)(ji+2)jA^i_j=(j-i+1)(j-i+2)\cdots j

那么只需要求所有乘积是 kk 的倍数的区间数量.显然,若一个区间乘积已经是 kk 的倍数了,后面不管加多少数,都是 kk 的倍数.因此,对于所有增加一个数之后恰好可以满足条件的区间,其贡献是这个区间后面数的数量 +1,因为可以增加任意数量(可以为 0)的后面的数.(为什么不加前面的数?加上前面的数的区间会在前面被计算过一次,再增加一次会导致重复)、

具体的,使用双指针来维护.一个指针 ii 指向当前区间的起点,一个指针 jj 指向当前区间的终点.若当前区间积还不是 kk 的倍数,那么尝试增加一个数满足限制,让指针 jj 前进,直到满足要求.此时这个区间的贡献是区间后面还有多少不在区间内的数,再 +1.那么这样以 ii 开头的区间全部统计完成了,让 ii 前进一步.

还有一些问题:如何判断一个区间的区间积是 kk 的倍数?

如果强行计算进行比较的话 __int128 都存不下,实际上,记录区间中所有数字乘积的分解质因数的结果,再和 kk 分解之后的结果进行比较即可.

那问题又来了,kk 的值域限制是 k109k \leq 10^9,这无法对 kk 进行质因数分解,怎么办呢?实际上,有一个小观察:kk 拥有一个质因数 pp 满足 p>np>n,则答案为 0.这也很好理解,如果 kk 拥有这样的 pp,则无论如何,乘积都不可能有大于每个数本身的质因数.对 kk 花费 k\sqrt{k} 的时间复杂度预处理一下即可.

分解质因数

实践中常常需要我们对于一个数进行分解质因数操作,这该如何实现呢?

分解质因数,首先需要知道质数.这里重点介绍线性筛质数这一种方法.

对于一个未确定的数,我们进行以下流程:

  • 若这个数未被标记过,则这个数是质数.
  • 接下来,无论这个数是否被标记过,都用它乘上所有已知的质数 pp,设所得结果为 xx,则将 xx 标记.可以证明,ppxx 的最小质因数.

以下程序在记录质数的同时,记录了每一个数的最小质因数(spfspf).

void calPrime() {
for (int i = 2; i <= N; i++) {
if (spf[i] == 0) {
primes.push_back(i);
}
for (int j = 0; j < primes.size(); j++) {
int pr = primes[j];
if (pr * i > N)
break;
spf[pr * i] = pr;
}
}
}

知道了每一个数的最小质因数之后,就可以快速对一个数分解质因数.

具体来说,当想要分解一个数 xx 时,将其除以其最小质因数 pp,这样在将 pp 作为贡献之后,原问题就转化为了规模更小的子问题:分解 xp\frac{x}{p},直到 spfx=0spf_x=0 时,就是该问题的边界,此时 xx 也是质数(或 0),加入贡献.

以下程序在上面程序的基础上,计算了 xx 的质因数:

typedef pair<int, int> pii;
vector<pii> ans;
while (true) {
if (spf[x] == 0) {
if (x != 1)
ans.push_back({x, 1});
break;
}
int pr = spf[x];
pii rd = {pr, 0};
while (true) {
if (x % pr)
break;
rd.second++;
x /= pr;
}
ans.push_back(rd);
}
return ans;

标程

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e6 + 100;
int n, k;
ll ans;
vector<int> primes;
vector<pii> stdpr;
int spf[N];
unordered_map<int, int> um;
void calPrime() {
for (int i = 2; i <= N - 10; i++) {
if (spf[i] == 0) {
primes.push_back(i);
}
for (int j = 0; j < primes.size(); j++) {
int pr = primes[j];
if (pr * i > N - 10)
break;
spf[pr * i] = pr;
}
}
}
vector<pii> calpr(int x) {
vector<pii> ans;
while (true) {
if (spf[x] == 0) {
if (x != 1)
ans.push_back({x, 1});
break;
}
int pr = spf[x];
pii rd = {pr, 0};
while (true) {
if (x % pr)
break;
rd.second++;
x /= pr;
}
ans.push_back(rd);
}
return ans;
}
bool isAllow() {
for (int i = 0; i < stdpr.size(); i++) {
int pr = stdpr[i].first;
if (um[pr] < stdpr[i].second) {
return false;
}
}
return true;
}
void insert(int x) {
vector<pii> vec = calpr(x);
for (int i = 0; i < vec.size(); i++) {
int pr = vec[i].first;
um[pr] += vec[i].second;
}
}
void remove(int x) {
vector<pii> vec = calpr(x);
for (int i = 0; i < vec.size(); i++) {
int pr = vec[i].first;
um[pr] -= vec[i].second;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
freopen("num.in", "r", stdin);
freopen("num.out", "w", stdout);
cin >> n >> k;
calPrime();
if (k == 1) {
cout << 1ll * (1 + n) * n / 2;
return 0;
}
for (int i = 0; i < primes.size(); i++) {
if (k % primes[i] == 0) {
pii t = {primes[i], 0};
while (k % primes[i] == 0) {
k /= primes[i];
t.second++;
}
if (t.second != 0)
stdpr.push_back(t);
}
}
if (k != 1) {
cout << 0;
return 0;
}
int i = 1, j = 1;
while (i <= n) {
if (isAllow()) {
ans += 0ll + n - i + 1;
remove(j);
j++;
if (i < j) {
i++;
insert(i);
}
} else {
i++;
insert(i);
}
}
cout << ans;
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

安吉D19 T1
https://blog.jerrylab.top/posts/problem/anji2026/D19/T1/
作者
Jerry
发布于
2026-08-20
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0