zoukankan      html  css  js  c++  java
  • 「SOL」旧试题 (LOJ/SDOI)

    数论+图论,妙不可言


    # 题面

    给定 (A,B,C),求:

    [sum_{i=1}^Asum_{j=1}^Bsum_{k=1}^Csigma_0(ijk) ]

    数据规模:(A,B,Cle 2 imes10^5)


    # 解析

    显然 (A,B,C) 三者等价,不妨设 (A) 是最大的

    关于 (sigma_0),有一个众所周知(比如我就不知道) 的结论:

    [sigma_0(ab)=sum_{imid a}sum_{jmid b}[iot j] ]

    三个数也是一样的,

    [sigma_0(abc)=sum_{imid a}sum_{jmid b}sum_{kmid c}[iot j][jot k][kot i] ]

    于是直接代回我们要求的式子:

    [egin{aligned} &sum_{i=1}^Asum_{j=1}^Bsum_{k=1}^Csum_{umid i}sum_{vmid j}sum_{wmid k}[uot v][vot w][wot u]\ =&sum_{u=1}^Asum_{v=1}^Bsum_{w=1}^C[uot v][vot w][wot u]leftlfloorfrac{A}{u} ight floorleftlfloorfrac{B}{v} ight floorleftlfloorfrac{C}{w} ight floor end{aligned} ]

    ([a,b])(a, b) 的最小公倍数。两数互质的条件可以进行反演:

    [egin{aligned} sum_{a}sum_{b}sum_{c}mu(a)mu(b)mu(c)sum_{[a,b]mid u}leftlfloorfrac{A}{u} ight floorsum_{[b,c]mid v}leftlfloorfrac{B}{v} ight floorsum_{[c,a]mid w}leftlfloorfrac{C}{w} ight floor end{aligned} ]

    定义 (f_b(a)) 如下:

    [f_b(a)=sum_{amid i}leftlfloorfrac{b}{i} ight floor ]

    可以 (O(Aln A)) 预处理出 (f_A(x), f_B(x), f_C(x)),于是要求的答案就是

    [sum_{a}sum_{b}sum_{c}mu(a)mu(b)mu(c)f_A([a,b])f_B([b,c])f_C([c,a]) ]

    但是我们发现这一波推导过后并没有什么用,直接算还是 (O(A^3)) 的。只是可以进行一些剪枝?

    • (mu(a),mu(b),mu(c)) 都必须非零,这样可以去掉很多无用的枚举;
    • (maxig{[a,b],[b,c],[c,a]ig}le A),好像也可以减掉一些枚举。

    这些剪枝足够吗?如果仍然三重 for 循环枚举,这样剪枝过后还是过不了。但是注意到这两个限制,第二个限制与两个变量相关,类似于图上的边

    • 条件一限制了图上的点的 (mu) 非零;
    • 条件二表示,若 ([a,b]le A),则图上存在 ((A,B)) 这条无向边。

    恰巧的是,我们计算的是一个三元组,体现在图上就是一个三元环

    (M) 为图的边数,我们可以用下面的方法 (O(Msqrt{M})) 地枚举所有三元环:

    • 统计每个点的度数,记作 (deg_u)
    • 给边定向,由度数大的点指向度数小的,若度数相等,则编号大的指向编号小的(编号小指向编号大也无所谓,反正要求二元组 ((deg_u,u)) 的大小比较唯一且具有传递性);
    • 枚举点 (u)
      • 标记 (u) 连出的所有点 (v)
      • 枚举 (u) 连出的点 (v)
        • 枚举 (v) 连出的点 (w),若 (w) 有标记,则找到三元环。

    三元环计数正确性、时间复杂度的证明

    根据边定向的规则,因为数对 $(deg_u, u)$ 的大小关系具有传递性且唯一确定,所以定向后的图一定是 DAG。

    这意味着三元环一定是由 $u o v o w$ 和 $u o w$ 构成的。且根据我们的枚举方式,这样的三元环仅能在枚举 $u$ 时计算到。所以计数不重不漏,正确性有保证。

    时间复杂度的证明类似分块。首先,标记点的时间复杂度为 $O(M)$,因为每条边只会遍历一次,不是复杂度的瓶颈。

    然后关注枚举的 $u o v o w$ 的中心点 $v$:

    • 若 $deg_vlesqrt{M}$,则 $w$ 只有 $O(sqrt{M})$ 个,$(u,v)$ 最多 $O(M)$ 个,则所有此类点 $v$ 的复杂度为 $O(Msqrt{M})$;
    • 若 $deg_vgtsqrt{M}$,因为 $u$ 连向 $v$,则 $deg_uge deg_vgtsqrt{M}$,这样的 $u$ 只有 $O(sqrt{M})$ 个,而 $(v,w)$ 仅有 $O(M)$ 个;则所有此类 $v$ 点的复杂度也为 $O(Msqrt{M})$。

    总复杂度 $O(Msqrt{M})$。

    回到这道题上来,我们就是要枚举图上的一个三元环然后计算答案。那么这个图的边数多大呢?打个表发现 (O(Msqrt{M})) 能过……(反正我不会证)

    于是按上述方法枚举即可。

    注意实际上枚举的不止有三元环,还存在 (a=b) 甚至是 (a=b=c) 的情况,不过这两种都可以直接暴力枚举,不是很重要。


    # 源代码

    /*Lucky_Glass*/
    #include <vector>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    #define con(typ) const typ &
    typedef long long llong;
    typedef pair<int, int> pii;
    
    inline int rin(int &r) {
    	int b = 1, c = getchar(); r = 0;
    	while ( c < '0' || '9' < c ) b = c == '-' ? -1 : b, c = getchar();
    	while ( '0' <= c && c <= '9' ) r = (r * 10) + (c ^ '0'), c = getchar();
    	return r *= b;
    }
    inline void write(con(int) w) {
    	if ( w > 9 ) write(w / 10), putchar((w % 10) ^ '0');
    	else if ( w < 0 ) putchar('-'), write(-w);
    	else putchar(w ^ '0');
    }
    
    const int N = 2e5 + 10, M = 2e6, MOD = 1e9 + 7;
    
    inline int iGCD(con(int) a, con(int) b) {return b ? iGCD(b, a % b) : a;}
    
    int na, nb, nc, ncas, nprm, nedg;
    int mu[N], prm[N], deg[N], edg[M][3], tag[N];
    llong fa[N], fb[N], fc[N];
    bool vis[N];
    vector<pii> lnk[N];
    
    void init() {
    	mu[1] = 1;
    	for (int i = 2; i < N; i++) {
    		if ( !vis[i] ) prm[++nprm] = i, mu[i] = -1;
    		for (int j = 1; j <= nprm && prm[j] * i < N; j++) {
    			vis[i * prm[j]] = true;
    			if ( i % prm[j] == 0 ) break;
    			mu[i * prm[j]] = -mu[i];
    		}
    	}
    }
    void proc(con(int) n, llong *arrf) {
    	for (int i = 1; i <= n; i++) {
    		arrf[i] = 0;
    		for (int j = i; j <= n; j += i)
    			arrf[i] += n / j;
    	}
    }
    int main() {
    	init();
    	rin(ncas);
    	while ( ncas-- ) {
    		rin(na), rin(nb), rin(nc);
    		int mx = max(na, max(nb, nc)), mn = min(na, min(nb, nc));
    		fill(fa, fa + mx + 1, 0);
    		fill(fb, fb + mx + 1, 0);
    		fill(fc, fc + mx + 1, 0);
    		proc(na, fa), proc(nb, fb), proc(nc, fc);
    		llong ans = 0;
    		// Part 1
    		for (int i = 1; i <= mn; i++)
    			ans += mu[i] * fa[i] * fb[i] * fc[i]; // mu[i] ^ 3 = mu[i]
    		// Part 2
    		fill(deg, deg + 1 + mx, 0), nedg = 0;
    		for (int g = 1; g <= mx; g++)
    			for (int i = 1; i * g <= mx; i++) if ( mu[i * g] )
    				for (int j = i + 1; 1ll * i * j * g <= mx; j++)
    					if ( mu[j * g] && iGCD(i, j) == 1 ) {
    						int lcm = i * j * g, ig = i * g, jg = j * g;
    						// iij
    						ans += mu[jg] * (fa[ig] * fb[lcm] * fc[lcm]
    							 + fa[lcm] * fb[ig] * fc[lcm]
    							 + fa[lcm] * fb[lcm] * fc[ig]);
    						// ijj
    						ans += mu[ig] * (fa[jg] * fb[lcm] * fc[lcm]
    							 + fa[lcm] * fb[jg] * fc[lcm]
    							 + fa[lcm] * fb[lcm] * fc[jg]);
    						// add edges at the same time :)
    						deg[ig]++, deg[jg]++;
    						edg[++nedg][0] = ig, edg[nedg][1] = jg;
    						edg[nedg][2] = lcm;
    					}
    		// Part 3
    		for (int i = 1; i <= mx; i++) lnk[i].clear();
    		for (int i = 1; i <= nedg; i++)
    			if ( deg[edg[i][0]] > deg[edg[i][1]]
    			  || (deg[edg[i][0]] == deg[edg[i][1]] && edg[i][0] < edg[i][1]) )
    				lnk[edg[i][0]].push_back(make_pair(edg[i][1], edg[i][2]));
    			else lnk[edg[i][1]].push_back(make_pair(edg[i][0], edg[i][2]));
    		for (int u = 1; u <= mx; u++) if ( mu[u] ) {
    			for (int i = 0, ii = (int)lnk[u].size(); i < ii; i++)
    				tag[lnk[u][i].first] = lnk[u][i].second;
    			for (int i = 0, ii = (int)lnk[u].size(); i < ii; i++) {
    				int v = lnk[u][i].first, luv = lnk[u][i].second;
    				for (int j = 0, _j = (int)lnk[v].size(); j < _j; j++)
    					if ( tag[lnk[v][j].first] ) {
    						int w = lnk[v][j].first, lvw = lnk[v][j].second,
    							luw = tag[w];
    						ans += mu[u] * mu[v] * mu[w] * (
    							fa[luv] * fb[luw] * fc[lvw]
    						  + fa[luv] * fb[lvw] * fc[luw]
    						  + fa[luw] * fb[luv] * fc[lvw]
    						  + fa[luw] * fb[lvw] * fc[luv]
    						  + fa[lvw] * fb[luv] * fc[luw]
    						  + fa[lvw] * fb[luw] * fc[luv]
    						);
    					}
    			}
    			for (int i = 0, ii = (int)lnk[u].size(); i < ii; i++)
    				tag[lnk[u][i].first] = 0;
    		}
    		write(int(ans % MOD)), putchar('
    ');
    	}
    	return 0;
    }
    

    THE END

    Thanks for reading!

    怎知晓 有人 玩世不恭掩愁肠
    取次花丛懒望 等剪烛西窗
    怎知晓 有人 独为异客在异乡
    嘴上逞强终不敢 举头看月光

    ——《从前有个衔玉教》By 星葵/鲜洋芋/溱绫西陌

    > Link 【0412乐正绫诞生祭】从前有个衔玉教-Bilibili

    欢迎转载٩(๑❛ᴗ❛๑)۶,请在转载文章末尾附上原博文网址~
  • 相关阅读:
    【转】用Linux命令行获取本机外网IP地址
    【转】5 Best Place to Learn Linux – Linux Tutorial Sites
    【转】linux shell 逻辑运算符、逻辑表达式详解
    ftp
    修改/创建计算机用户名、密码
    SCRIPT429: Automation 服务器不能创建对象
    Tomcat 加载外部dll时如何配置
    查看电脑MAC地址
    访问windows共享无法分配内存问题解决
    打包
  • 原文地址:https://www.cnblogs.com/LuckyGlass-blog/p/14677477.html
Copyright © 2011-2022 走看看