• BZOJ4018: 小Q的幻想之乡


    Description

    背景

    有一天,小Q梦见自己来到了理想国的幻想之乡。

    描述

    有一天,小Q梦见自己来到了理想国的幻想之乡。幻想乡有无穷户居民,第i个家庭住在编号为i的房屋里,编号从1开始,到正无穷。

    居民们的房屋之间有着许多种道路,其中第k种道路只连接在编号为k的倍数且在k的倍数中连续的房屋之间。例如第1种道路连接在编号为(1,2),(2,3),(3,4)…的房屋之间,而第3种道路只连接在编号为(3,6),(6,9),(9,12)…的房屋之间。

    小Q要抓紧睡梦的时间来拜访幻想之乡中的贵人,他希望你能帮他完成这场幻想之旅。他经常会从某个编号为i的房屋只走某种道路快速到达某个编号为j的房屋,比如说从编号为4的房屋走到编号为8的房屋,可以走4->5->6->7->8,也可以走4->6->8,甚至可以走4->8一步到达目的地。

    他很好奇,如果他的起点房屋的编号是不大于n的,终点房屋的编号是不大于m的,对于所有可能的起点与终点,他最少会走多少条路,注意他的移动只会选择一种道路。

    为了避免精度误差,他希望你能告诉他所有可能的起点与终点所对应的经过的最少边数之和。

    由于这个数可能超过10^18,但是不会很大,所以你只需要求出它对两个质数10^9+7和10^9+9的模值即可,小Q的数学很好,他会算出原来的答案

    Input

    第一行一个正整数T,表示小Q有T组好奇的问题。

    接下来是T组问题,每组问题占一行,共两个正整数n, m,空格隔开。

    Output

    共T行,每行两个空格隔开的整数,表示每组问题分别模10^9+7和10^9+9的答案。

    Sample Input

    2
    3 3
    2 4

    Sample Output

    8 8
    9 9

    HINT

    100%的数据 T <= 1000, n, m <= 2*10^6

    Solution

    题意有点迷...其实就是求

    [sum_{i=1}^nsum_{j=1}^mfrac{|i-j|}{gcd(i,j)} ]

    大力推式子

    然后就能出来这个(写这题的应该都能推到这里吧...)

    [egin{aligned} &设sum(n,m)=sum_{i=1}^nsum_{j=1}^{m}|i-j|\ &则原式=sum_{T=1}^{n}sum(frac{n}{T},frac{m}{T})sum_{k|T}kmu(k) end{aligned} ]

    后面那段是个积性函数直接线性筛就好。
    怎么筛肯定都会...不会就去重学一下线性筛吧..反正就是分类讨论。这里证明一下这个为什么是个积性函数
    我们设

    [f(i)=imu(i) ]

    那么

    [egin{aligned} &设x,y互质\ &f(xy)\ &=x*y*mu(xy)\ &=x*y*mu(x)*mu(y)\ &=x*mu(x)*y*mu(y)\ &=f(x)f(y) end{aligned} ]

    所以说这是个积性函数。
    我们用狄利克雷卷积把它跟恒等函数(I)卷起来

    [egin{aligned} &f*I\ &=sum_{d|n}f(d)\ &=sum_{d|n}dmu(d) end{aligned} ]

    两个积性函数的狄利克雷卷积也是积性函数。
    证毕。

    重点在前面那一块。

    我们考虑来拆一下。

    (n=m)

    [egin{aligned} sum(n,m) &=sum_{i=1}^nsum_{j=1}^{n}|i-j|\ &=2*sum_{i=1}^nsum_{j=1}^{i}i-j\ &=2*sum_{i=1}^{n}left(sum_{j=1}^{i}i-sum_{j=1}^{i}j ight)\ &=2*sum_{i=1}^{n}left(i^2-frac{i(i+1)}{2} ight)\ &=sum_{i=1}^{n}2i^2-i(i+1)\ &=sum_{i=1}^{n}i(2i-i-1)\ &=sum_{i=1}^{n}i^2-i\ &=sum_{i=1}^ni^2-sum_{i=1}^ni\ &=frac{n(n+1)(2n+1)}{6}-frac{n(n+1)}{2}\ &=frac{n(n+1)(n-1)}{3} end{aligned} ]

    把这个玩意看成一个矩形,那么当我们解决了一个正方形之后,显然就剩下一个小的矩形,我们来处理一下这个小矩形。

    不妨设(n<m)

    [egin{aligned} &sum_{i=1}^{n}sum_{j=n+1}^{m}j-i\ &=sum_{i=1}^{n}left(sum_{j=n+1}^{m}j-sum_{j=n+1}^mi ight)\ &=sum_{i=1}^{n}left(frac{(n+1+m)(m-n)}{2}-(m-n)i ight)\ &=frac{n(n+1+m)(m-n)}{2}-frac{n(n+1)(m-n)}{2}\ &=frac{nm(m-n)}{2} end{aligned} ]

    所以这就是网上那个式子的由来了。。。但是我看其他blog都完全没有推导。

    [egin{aligned} &sum(n,m)(n<m)\ &=sum_{i=1}^nsum_{j=1}^{n}|i-j|\ &=frac{n(n+1)(n-1)}{3}+frac{nm(m-n)}{2} end{aligned} ]

    然后不知道为什么还要两个模数。。。完全就是增加代码量。。。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define N 2000010
    const ll inf = 1e18;
    const ll mod1 = 1e9 + 7;
    const ll mod2 = 1e9 + 9;
    int T, cnt;
    ll n, m, F[2][N];
    int p[N], vis[N];
    
    void init() {
    	F[0][1] = F[1][1] = 1;
    	for(int i = 2; i < N; ++i) {
    		if(!vis[i]) p[++cnt] = i, F[0][i] = (1 - i + mod1) % mod1, F[1][i] = (1 - i + mod2) % mod2;
    		for(int j = 1; j <= cnt && p[j] * i < N; ++j) {
    			vis[i * p[j]] = 1;
    			if(i % p[j] == 0) {
    				F[0][i * p[j]] = F[0][i];
    				F[1][i * p[j]] = F[1][i];
    				break;
    			}
    			F[0][i * p[j]] = F[0][i] * F[0][p[j]] % mod1;
    			F[1][i * p[j]] = F[1][i] * F[1][p[j]] % mod2;
    		}
    	}
    	for(int i = 1; i < N; ++i) F[0][i] += F[0][i - 1], F[0][i] %= mod1, F[1][i] += F[1][i - 1], F[1][i] %= mod2;
    }
    
    ll calc1(ll n, ll m, ll mod) {
    	if(!n || !m) return 0;
    	if(n > m) swap(n, m);
    	ll sum1 = n * (n + 1) * (n - 1) / 3 % mod;
    	ll sum2 = n * m * (m - n) / 2 % mod;
    	return (sum1 + sum2) % mod;
    }
    
    ll calc_1(ll n, ll m) {
    	ll ans = 0;
    	for(ll l = 1, r; l <= n; l = r + 1) {
    		r = min(n / (n / l), m / (m / l));
    		ans += calc1(n / l, m / l, mod1) * (F[0][r] - F[0][l - 1] + mod1) % mod1;
    		ans %= mod1;
    	}
    	return ans;
    }
    
    ll calc_2(ll n, ll m) {
    	ll ans = 0;
    	for(ll l = 1, r; l <= n; l = r + 1) {
    		r = min(n / (n / l), m / (m / l));
    		ans += calc1(n / l, m / l, mod2) * (F[1][r] - F[1][l - 1] + mod2) % mod2;
    		ans %= mod2;
    	}
    	return ans;
    }
    
    int main() {
    	scanf("%d", &T);
    	init();
    	while(T--) {
    		scanf("%lld%lld", &n, &m);
    		if(n > m) swap(n, m);
    		printf("%lld %lld
    ", calc_1(n, m), calc_2(n, m));
    	}
    }
    
  • 相关阅读:
    简单通讯聊天 群聊功能 Windows下的客户端 Linux下的epoll服务器
    Windows客户端 Linux服务器通讯 字符编码问题
    C++时间标准库时间time和系统时间的使用
    Window7系统安装Ubuntu16双系统
    Window7 系统下重新建立一个新分区
    UltraISO(软碟通) 制作U盘启动盘
    Python 列表反转显示方法
    HTML,CSS,JS个别知识点总结
    Git 创建版本库并实现本地上传数据到GitHub库
    Python爬虫数据保存到MongoDB中
  • 原文地址:https://www.cnblogs.com/henry-1202/p/10350266.html
Copyright © 2020-2023  润新知