CF1008D Pave the Parallelepiped 容斥+组合数学+状态压缩

题目链接传送门φ(>ω<*)

You are given a rectangular parallelepiped with sides of positive integer lengths A, B and C

Find the number of different groups of three integers (a, b, c) such that 1≤a≤b≤c and parallelepiped A×B×C can be paved with parallelepipeds a×b×c

Note, that all small parallelepipeds have to be rotated in the same direction.

For example, parallelepiped 1×5×6

can be divided into parallelepipeds 1×3×5, but can not be divided into parallelepipeds 1×2×3

Input

The first line contains a single integer t

(1≤t≤10^5) — the number of test cases.Each of the next tlines contains three integers A, B and C (1≤A,B,C≤10^5) — the sizes of the parallelepiped.

Output

For each test case, print the number of different groups of three points that satisfy all given conditions.

不得不说这是一道非常好的题。题目很好读,给你大长方体长宽高ABC,问你能够拼成大正方体的小正方体(同种类恰好拼成)的种类是多少。很容易想到,小正方体的长宽高分别都是大正方体的长宽高的因数。所以第一眼就觉得只要整个的单个因子数乘起来然后减掉重复的就好了。

那么请问怎么减掉呢?一般的容斥通法可不行,因为他们重复的次数是不确定的,这么做势必造成结果错误。

所以我们就要每条边来看了。每条边无非只有7种状况

001:只是A的约数,不是BC的约数,这是可以容斥的。下同。

010:只是B的因数。

100:只是C的因数。

011:只是AB的因数。

101:只是AC的因数。

110:只是BC的因数。

111:同时是ABC的因数

我们现在就可以枚举每条边的状态了,三重循环7*7*7次。那么接下来我们考虑什么样的边是满足条件的边呢:

给小长方体一个合适的翻转,使三条边至少分别是ABC点的因子。怎么做呢?上面我们按位处理了每条边的状态,所以只要没枚举一下变得顺序(翻转正方形)使得和ABC一一对应(对应位为1)

bool check(int a,int b,int c){
    if((a&1) && (b&2) && (c&4))
        return true;
    if((a&1) && (c&2) && (b&4))
        return true;
    if((b&1) && (a&2) && (c&4))
        return true;
    if((b&1) && (c&2) && (a&4))
        return true;
    if((c&1) && (a&2) && (b&4))
        return true;
    if((c&1) && (b&2) && (a&4))
        return true;
    return false;
}

好了接下来我们找到合适的三边了,那么怎么计算此时的种类呢?这也是本体的一大亮点。

1.如果a,b,c是不同的状态得来的,那么ok没有重复。每种数量选一个,也就是乘起来就好啦~

2.但是如果有两个数或者三个数是相同的状态,这就要求我们从一定数量中选择有重复个元素。举个例子,如果ab状态相同,都是cond[s],那么我们应该从cond[s]中选两个,并且可以重复即a选了k,b也选了k
这就需用到有重复的组合公式:
如果从n个元素中选择r个有重复元素,其公式为:

C_{n+r-1}^r另外,我们用use数组将计算过程一般化,use[i]即为r。请好好体会。这里非常巧妙。

哦对了还要注意组合数会爆long long的呢(*´゚∀゚`)ノ

上代码 哎呀好困呀就不写注释了但是代码一定能看得懂啦~~~(一开始过了的时候代码写的贼乱。。。qwq)

#include<iostream>
#include<cstdio>
#include<cstring>
typedef long long ll;
using namespace std;

const int maxn = 1e5 + 10;
int fac[maxn];

void fac_table(int n){
	for(int i = 1;i < n;i++)
		for(int j = i;j < n;j+=i)
			fac[j]++;
	return;
}
ll C(int n,int m){
	ll ans = 1;
	for(int i = 1;i <= m;i++){
		ans = ans*(n-i+1)/i;
	}
	return ans;
}

int gcd(int a,int b){
	if(b == 0)
		return a;
	return gcd(b,a%b);
}

bool check(int a,int b,int c){
    if((a&1) && (b&2) && (c&4))
        return true;
    if((a&1) && (c&2) && (b&4))
        return true;
    if((b&1) && (a&2) && (c&4))
        return true;
    if((b&1) && (c&2) && (a&4))
        return true;
    if((c&1) && (a&2) && (b&4))
        return true;
    if((c&1) && (b&2) && (a&4))
        return true;
    return false;
}

int condi[10],use[10];

int main(){
	fac_table(maxn);
	ll T;
	int x,y,z;ll ans;
	scanf("%lld",&T);
	while(T--){
		scanf("%d%d%d",&x,&y,&z);
		int xy = gcd(x,y);
		int xz = gcd(x,z);
		int yz = gcd(y,z);
		int xyz = gcd(xy,z);
		
		condi[1] = fac[x] - fac[xy] - fac[xz] + fac[xyz];//001
		condi[2] = fac[y] - fac[xy] - fac[yz] + fac[xyz];//010
		condi[4] = fac[z] - fac[xz] - fac[yz] + fac[xyz];//100
		condi[3] = fac[xy] - fac[xyz];//011
		condi[5] = fac[xz] - fac[xyz];//101
		condi[6] = fac[yz] - fac[xyz];//110
		condi[7] = fac[xyz];
		
		ans = 0;
		for(int a = 1;a < 8;a++)
			for(int b = a;b < 8;b++)
				for(int c = b;c < 8;c++)
					if(check(a,b,c) == true){
						memset(use,0,sizeof(use));
						use[a]++;use[b]++;use[c]++;
						ll tmp = 1;
						for(int i = 1;i < 8;i++)
							if(use[i] != 0)
								tmp = tmp*C(condi[i] + use[i]-1,use[i]);
						if(tmp > 0)
						ans += tmp;
					}
		printf("%lld\n",ans);
	}
	return 0;
}

嗯 桃酱去睡觉了。某人好梦。一起奋斗的人,也好梦。

明天早上的偏微分方程看来要迟到了...希望不会被队友锤...逃)

猜你喜欢

转载自blog.csdn.net/qq_37136305/article/details/81212849