[CF1007B]Pave the Parallelepiped[组合计数+状态压缩]

题意

\(t\) 组询问,给你 \(A, B, C\) ,问有多少组三元组 \((a, b, c)\) 满足他们任意排列后有: \(a|A,\ b|B,\ c|C\)

\(A,B,C,t\leq 10^5\)

分析

  • 我们把三个数的所有因子用 \(2^3 - 1\) 个状态表示这个数是 \(A,B,C\) 中的哪几个数字的因子。

  • 按照从小到大的顺序枚举3个数对应的集合,首先保证能够找到一种对应方式(每个数对应是谁的因子),相同的数集利用插板法计算方案避免重复。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cctype>
#include<vector>
#include<queue>
#define For(s) for(int s=1;s<8;s++)
using namespace std;
const int N=1e5 + 7;
typedef long long LL;
int n,T;
int sz[8],A[8],f[8],b[8]={0,1,1,2,1,2,2,3};
LL ans,tmp;
int gcd(int a,int b){
  if(!b) return a;
  return gcd(b,a%b);    
}
int get(int x){
    int res=0;
    for(int i=1;i<=sqrt(x);i++)if(x%i==0){
      res++;
      if(i*i!=x) res++;
    }
    return res;
}
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;
}
LL C(int n,int m){
    if (m == 0) return 1;
    if (m == 1) return n;
    if (m == 2) return 1ll * n * (n - 1) / 2;
    if (m == 3) return 1ll * n * (n - 1) * (n - 2) / 6;
}
void work(){
     scanf("%d%d%d",&A[0],&A[1],&A[2]);
     ans=0; memset(sz,0,sizeof(sz));
     memset(f,0,sizeof(f));
     For(S){
        for(int i=0;i<3;i++) if(S>>i&1){
            if(!f[S]) f[S]=A[i];
            else f[S]=gcd(f[S],A[i]);
        }
        f[S]=get(f[S]);
     }
     For(s) For(S)if((S&s)==s){
        int cnt=b[S]-b[s];
        sz[s]+=f[S]*(cnt&1?-1:1);
     }
     For(s1)for(int s2=s1;s2<8;s2++)for(int s3=s2;s3<8;s3++){
       if(check(s1,s2,s3)){
          tmp=1;int cnt=1,cho=233;
          if(s1==s2) cho=s1,cnt++;
          if(s2==s3) cho=s2,cnt++;
          if(s1^cho) tmp*=sz[s1];
          if(s2^cho) tmp*=sz[s2];
          if(s3^cho) tmp*=sz[s3];
          if(cnt^1) tmp*=C(sz[cho]+cnt-1,cnt);
          ans+=tmp;
       }
     }
     printf("%lld\n",ans);
}
int main(){
    scanf("%d",&T);
    while(T--) work();
    return 0;
}

转载于:https://www.cnblogs.com/yqgAKIOI/p/10136068.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值