【洛谷】4917:天守阁的地板【欧拉函数的应用】【lcm与gcd】【同除根号优化】

P4917 天守阁的地板

题目背景

在下克上异变中,博丽灵梦为了找到异变的源头,一路打到了天守阁

异变主谋鬼人正邪为了迎击,将天守阁反复颠倒过来,而年久失修的天守阁也因此掉下了很多块地板

异变结束后,恢复了正常大小的小碗回到了天守阁,想要修复这里的地板,她需要知道自己要采购的地板数量(一个惊人的数字),于是,她找到了精通oi的你来帮忙

题目描述

为了使万宝槌能发挥出全部魔力,小碗会将买来的地板铺满一个任意边长的正方形(地板有图案,因此不允许旋转,当然,地板不允许重叠)来达到最大共鸣

现在,她能够买到规格为ab的地板,为了省钱,她会购买尽可能数量少的地板

现在,她想知道对于每一对a,b(1a,bn),她最少需要购买的地板数量

由于输出可能很大,所以你只需要输出所有答案的乘积即可,为了避免高精度,小碗很良心的让你将答案对19260817取模

输入输出格式

输入格式:

第一行一个整数T,表示数据组数
下面T行,每行一个整数n

输出格式:

T行,每行一个整数,表示取模后的答案

输入输出样例

输入样例#1: 复制
4
1
2
3
100
输出样例#1: 复制
1
4
1296
18996121

说明

样例解释:
对于n=1,a,b仅有(1,1)一种情况,只需要一块11的地板,答案为1

对于n=2,a,b有(1,1),(1,2),(2,1),(2,2)四种情况,分别需要一块(1*1),两块(12),两块(21),一块(22)的地板,答案为1221=4

追加解释:a,b有四种取值,分别是(1,1),(1,2),(2,1),(2,2)

当只能买到11的地板时,需要一块(本身就是正方形)
当只能买到12的地板时,需要两块(两块拼在一起组成22的正方形)
当只能买到21的地板时,需要两块(两块拼在一起组成22的正方形)
当只能买到22的地板时,需要一块(本身就是正方形)

答案就是这些数的乘积,即4

第一天停课的早上,在$yuli$dalao已经a掉12道题的同时,终于把这道题搞清楚了。

出题人颠覆了我对noip的看法!

抱头痛哭QAQ

题解写的确实清楚,但这这这noipd2t1考的知识点要不要太多aaa!!!欧拉函数+逆元线性筛,$gcd$和$lcm$相关公式推导,同除$O(sqrt{n})$优化.....我大概也就到了能把第一步式子推出来的地步吧....


先考虑单个询问的情况,可以得到如下结论:
用$a*b$规格的地板摆成的正方形的边长最小是$lcm(a,b)$(木板不允许旋转),所以需要最小的木板数量是$frac{lcm(a,b)}{a}*frac{lcm(a,b)}{b}$

问题中说要把所有可能的$(a,b)$的答案求乘积,那么问题就转换成了求

首先你必须知道

(唯一分解后易证)

因此

先考虑分子:

 

所以可以先$O(n)$预处理出阶乘$fac(i)$,用快速幂$O(logn)$算出分子

接下来考虑分母

为了方便,可以先求出

再平方

考虑枚举$d$,那么我们求出对于每个$d$有多少对$(i,j)$满足$gcd(i,j)=d$再套用快速幂即可

显然$i,j$都是$d$的倍数是必要条件,所以设$i=k_1d,j=k_2d(k1,k2≤lfloor frac{n}{d} floor)$
那么当且仅当$gcd(k1,k2)=1$即$k1,k2$互质时符合$gcd(i,j)=d$的条件(否则$gcd$可以扩大为$d*gcd(k1,k2)$
不妨令 $k1>k2$,那么符合条件的点对数 为每个在范围内的$k1$,小于它且与它互质的数的个数的和(即$sumφ$),所以真正的答案就是

 

好了,到现在复杂度为$O(n+Tnlogn)$,可以拿到$60$分的好成绩(雾)

最后一个优化:
不难发现,$lfloor frac{n}{d} floor$的取值只有$2sqrt{n}$种,那么可以把$lfloor frac{n}{d} floor$相等的所有d放在一起处理

假设某一段从i到j的所有数的这个值都等于x,那么这一段的乘积就等于

那么先预处理出$19260817$的逆元$inv(i)$,这一段的答案就是$(fac(j)*inv(fac(i-1)))^{sum(x)*2-1}$

这样处理后的最终复杂度是$O(n+Tsqrt{n}log(n))$,可以愉快的通过此题

#include<bits/stdc++.h>
#define LL long long
#define mod 19260817
#define maxn 1000000
using namespace std;

LL mpow(LL a, LL b) {
    LL ans = 1;
    for(; b; b >>= 1, a = a * a % mod)
        if(b & 1)    ans = ans * a % mod;
    return ans;
}

int isnot[maxn+5], prime[maxn+5], t;
int fac[maxn+5], inv[mod+5];
LL phi[maxn+5];
void init() {
    isnot[1] = 1; phi[1] = 1;
    for(int i = 2; i <= maxn; i ++) {
        if(!isnot[i]) {
            prime[++t] = i;
            phi[i] = i - 1;
        }
        for(int j = 1; j <= t; j ++) {
            if(i * prime[j] > maxn)    break;
            isnot[i * prime[j]] = 1;
            if(i % prime[j])    phi[i * prime[j]] = phi[i] * (prime[j] - 1);
            else {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
        }
    }
    for(LL i = 2; i <= maxn; i ++)    phi[i] += phi[i-1];
    fac[0] = 1;
    for(LL i = 1; i <= maxn; i ++)    fac[i] = (LL)(fac[i-1] * i) % mod;
    inv[0] = inv[1] = 1;
    for(LL i = 2; i < mod; i ++)    inv[i] = (LL)((LL)(-(mod / i) * (LL)inv[mod % i]) % mod + mod) % mod;
}

int main() {
    int T;
    scanf("%d", &T);
    init();
    while(T --) {
        int n;
        scanf("%d", &n);
        LL ans1 = mpow(fac[n], 2 * n);
        LL ans2 = 1;
        for(LL d = 1, L; d <= n; d = L + 1) {
            LL p = (LL)2 * phi[n/d] - 1;
            L = n / (n / d);///////////////////////////////同除优化
            ans2 = (LL)ans2 * mpow((LL)fac[L] * (LL)inv[fac[d-1]] % mod, p) % mod;
        }
        ans2 = ans2 * ans2 % mod;
        ans2 = inv[ans2];
        printf("%lld
", ans1 * ans2 % mod);
    }
    return 0;
} 

!!!愉快个猪皮怪物!!(不过好在复习了线性筛和一些小公式)

还有一个知识点在这里用到。

BZOJ2818:

2818: Gcd

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 8803  Solved: 3908
[Submit][Status][Discuss]

Description

给定整数N,求1<=x,y<=N且Gcd(x,y)为素数的
数对(x,y)有多少对.

Input

一个整数N

Output

如题

Sample Input

4

Sample Output

4

HINT

hint

对于样例(2,2),(2,4),(3,3),(4,2)


1<=N<=10^7

Source

[Submit][Status][Discuss]

上面题解也有讲到,设$gcd(x,y)=d$,那么题目就是求对于每个$d$,$gcd(frac{x}{d},frac{y}{d})=1$的对数。设$frac{y}{d}=k1>frac{x}{d}=k2$,我们求$φ$的前缀和,那么就是求$φ(k1)*2-1$($1$会多一个重复)(对于$k1=lfloor frac{n}{d} floor$),然后枚举$d$就可以了。

这道题要求$d$是素数,所以枚举素数就行了。

#include<bits/stdc++.h>
#define LL long long
#define maxn 10000000 + 5
using namespace std;

LL phi[maxn];
int isnot[maxn], prime[maxn], n, t;

void init() {
    isnot[1] = 1;
    phi[1] = 1;
    for(int i = 2; i <= n; i ++) {
        if(!isnot[i])
            prime[++t] = i, phi[i] = i - 1;
        for(int j = 1; j <= t; j ++) {
            int to = prime[j] * i;
            if(to > n)    break;
            isnot[to] = 1;
            if(i % prime[j] == 0) {
                phi[to] = phi[i] * prime[j];//////////记住就好! 
                break;
            } else phi[to] = phi[i] * (prime[j] - 1);//////互质用积性函数的性质 
        }
    }
    for(int i = 1; i <= n; i ++)    phi[i] += phi[i-1];
}

int main() {
    scanf("%d", &n);
    init();
    LL ans = 0;
    for(int i = 1; i <= t && prime[i] <= n; i ++)
        ans += (phi[n/prime[i]] * 2 - 1);
    printf("%lld", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/wans-caesar-02111007/p/9752913.html