0%

[CF1986G2] Permutation Problem

神秘的消 题。

题目链接

题意

给定一个长为 的排列 ,求 对数。

题解

考虑令 ,并使用

那么 相当于

注意到此时 互质,且 互质,所以上述条件相当于

考虑忽略 的条件,先求出所有满足的 ,再去掉 的情况。

维护数组 表示所有 中,满足 的数量。

那么可以发现,对于一个 来说,合法的 的数量便是

(自己思考一下 的条件就能明白这为什么是对的。)

考虑这样做的时间复杂度,首先 是一个 的数组,显然开不下,所以需要使用 map 维护。

再来计算处理 数组的复杂度,可以发现最坏需要在 map 中插入 次,所以这部分复杂度是 的。

查询的复杂度完全相同,这样就得到了一个 的做法,可以通过 G1。

核心代码大概长这个样子(其中 存储所有 的因数):

1
2
for(int i=1;i<=n;i++) for(int j:d[y[i]]) vis[x[i]][j]++;
for(int i=1;i<=n;i++) for(int j:d[y[i]]) ans+=vis[j][x[i]];

如果要优化这个做法,必须要去掉一个 ,考虑避免使用 map 来消掉一个

想想我们为什么要用 map,原因是 的数组开不下。

但是一个 的数组是开的下的,那么我们枚举一个 ,考虑能否计算所有 的贡献。

维护 是容易的,找到所有 ,像上面一样插入即可。

查询也是容易的,发现 只会给所有 产生贡献,那么我们直接枚举 就可以了。

特别地,这里我们可以先枚举 再枚举 ,这样这里的枚举总复杂度是 的。

所以我们得到了一个复杂度是 的做法。非常神奇。

代码

上述做法并没有保证 ,所以需要先把 的情况去掉。

容易发现 的情况只会在 时恰好被计算一次(这个条件与 等价),减掉之后就只剩 的情况了,显然 的方案数相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
#define debug(x) cerr<<#x<<"="<<x<<endl
using namespace std;
using ll=long long;
using ld=long double;
using pli=pair<ll,int>;
using pi=pair<int,int>;
template<typename A>
using vc=vector<A>;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
vc<int>num1[500001];
vc<int>num2[500001];
int val[500001];
vc<int>d[500001];
int a[500001];
int n;
int main()
{
for(int i=1;i<=500000;i++) for(int j=1;i*j<=500000;j++) d[i*j].push_back(i);
int T=read();
while(T--)
{
n=read();
for(int i=1;i<=n;i++) a[i]=read();

ll ans=0;
for(int i=1;i<=n;i++)
{
int x=i,y=a[i],g=__gcd(x,y);
x/=g,y/=g;
num1[x].push_back(y);
num2[y].push_back(x);
if(y%x==0) ans--;
}
for(int x=1;x<=n;x++)
{
for(int j:num1[x]) for(int k:d[j]) val[k]++;
for(int y=x;y<=n;y+=x) for(int k:num2[y]) ans+=val[k];
for(int j:num1[x]) for(int k:d[j]) val[k]--;
}
for(int i=1;i<=n;i++) num1[i].clear(),num2[i].clear();
printf("%lld\n",ans/2);
}
return 0;
}

感谢观看!