『题解』LibreOJ #563. Snakes 的 Naïve Graph
#563. 「LibreOJ Round #10」Snakes 的 Naïve Graph
Description
-
有一张二分图 \(G(m)\),其中有 \((m - 1)\) 个黑色点与 \((m - 1)\) 个白色点。黑点 \(i\) 与白点 \(j\) 有一条无向边,当且仅当下列条件至少有一条成立:
-
\(i = j\)
-
\(i\cdot j \equiv 1\pmod m\)
-
-
令 \(f[G(m)]\) 表示 \(G(m)\) 的本质不同的最大匹配的个数。
-
共 \(q\) 组询问,每组询问给出 \(l, r\),求 \(\sum_{i = l}^r f[G(i)]\bmod 311021\)。
-
\(1\le l\le r\le 10^7, 1\le q\le 10^5\)。
Solution
一道披着二分图外表的 \(\text{Math}\)。
校内 OJ 甚至把这题放入了二分图专题内(
\(i\cdot j \equiv 1\pmod m\) 说明 \(i\) 和 \(j\) 互为模 \(m\) 下的逆元,那么必须满足 \(i, j\) 都与 \(m\) 互质。
当 \(m\ge 2\) 时,\(1\sim m - 1\) 中与 \(m\) 互质的数有 \(\varphi(m)\) 个。
由于一个数的逆元唯一,故一个数不可能成为多个数的逆元,那么最大匹配要么就是黑 \(i\) 连白 \(i\),要么就是黑 \(i\) 连白 \(i^{-1}\)。
- 如果黑 \(i\) 连白 \(i\),那么黑 \(i^{-1}\) 就不能连白 \(i\) 了,只能连白 \(i^{-1}\);
- 如果黑 \(i\) 连白 \(i^{-1}\),那么同理可得黑 \(i^{-1}\) 连白 \(i\)。
所以每对逆元 \(2\) 种连边方法,记逆元对数为 \(n\),则 \(f[G(m)] = 2^n\)。
但是当 \(i^2 \equiv 1 \pmod m\) 时被算了 \(2\) 次,需要减掉一次。
将 \(m\) 分解质因数
根据中国剩余定理
所以分别求出每一个同余方程的解数,然后相乘即可。
现在考虑
根据平方差公式
-
当 \(p > 2\) 时,\((x - 1)\) 和 \((x + 1)\) 不可能同时为 \(p\) 的倍数,\(x - 1 \equiv 0\pmod {p^\alpha}\) 或 \(x + 1\equiv 0 \pmod {p^\alpha}\),\(2\) 种;
-
当 \(p = 2\) 时,\((x - 1)\) 和 \((x + 1)\) 奇偶性相同,则必须均为 \(2\) 的倍数:
- \(\alpha = 1\):\(\bmod 2\) 余数均为 \(0\),共 \(1\) 种;
- \(\alpha = 2\):\(\bmod 4\) 余数分别为为 \(0, 2\) 或 \(2, 0\),共 \(2\) 种;
- \(\alpha \ge 3\):注意到不可能均为 \(4\) 的倍数,所以 \(\bmod 2^\alpha\) 余数分别为 \(0, 2\) 或 \(2^{\alpha - 1}, 2\) 或 \(2, 2^{\alpha - 1}\) 或 \(2, 0\),共 \(4\) 种;
于是用线性筛,分别记录 \(phi_n\) 表示 \(\varphi(n)\),\(tot_n\) 表示 \(n\) 除了 \(2\) 外含有多少个本质不同质因数,\(pf2_n\) 表示 \(n\) 含有多少个质因数 \(2\)。
再预处理出 \(pw2_n\) 表示 \(2^n\bmod 311021\)。
那么就可以计算 \(dif_n\) 表示满足 \(x^2\equiv 1\pmod n\) 的 \(x\) 的个数,于是 \(f[G(m)] = 2^{\left(\frac{phi_m - dif_m}{2}\right)} = pw2_{\left(\frac{phi_m - dif_m}{2} \right)}\)。
有一个问题,此处的 \(phi\) 和 \(dif\) 都是不能取模的,但计算 \(dif\) 时要用到 \(pw2\),而 \(pw2\) 又必须取模。
其实当 \(n\le 10^7\) 时 \(\max\{tot_n\} \le 7\),\(2^7\) 是不会超过模数的,所以正常取模即可。
最后记录前缀和就做完了。
Code
// 18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
#include <cstring>
#define Debug(x) cout << #x << "=" << x << endl
typedef long long ll;
using namespace std;
const int MAXN = 1e7 + 5;
const int N = 1e7;
typedef int arr[MAXN];
const int MOD = 311021;
int add(int a, int b) {return (a + b) % MOD;}
int sub(int a, int b) {return (a - b + MOD) % MOD;}
int mul(int a, int b) {return (ll)a * b % MOD;}
arr p, phi, pf2, tot, pw2, dif, f, sum;
bool vis[MAXN];
void pre()
{
phi[1] = 1;
for (int i = 2; i <= N; i++)
{
if (!vis[i])
{
p[++p[0]] = i;
phi[i] = i - 1;
if (i == 2)
{
pf2[i] = 1;
}
else
{
tot[i] = 1;
}
}
for (int j = 1; j <= p[0] && i * p[j] <= N; j++)
{
int k = i * p[j];
vis[k] = true;
if (i % p[j] == 0)
{
phi[k] = phi[i] * p[j];
if (p[j] == 2)
{
pf2[k] = pf2[i] + 1;
tot[k] = tot[i];
}
else
{
pf2[k] = pf2[i];
tot[k] = tot[i];
}
break;
}
phi[k] = phi[i] * phi[p[j]];
if (p[j] == 2)
{
pf2[k] = 1;
tot[k] = tot[i];
}
else
{
tot[k] = tot[i] + 1;
}
}
}
pw2[0] = 1;
for (int i = 1; i <= N; i++)
{
pw2[i] = mul(pw2[i - 1], 2);
}
for (int i = 1; i <= N; i++)
{
dif[i] = pw2[tot[i]];
if (pf2[i] == 2)
{
dif[i] <<= 1;
}
else if (pf2[i] >= 3)
{
dif[i] <<= 2;
}
f[i] = pw2[(phi[i] - dif[i]) >> 1];
sum[i] = add(sum[i - 1], f[i]);
}
}
int main()
{
pre();
int q;
scanf("%d", &q);
while (q--)
{
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", sub(sum[r], sum[l - 1]));
}
return 0;
}