Codeforces Round #497 (Div. 2) D. Pave the Parallelepiped

链接

http://codeforces.com/contest/1008/problem/D

大意

给你 (A,B,C) ( A , B , C ) ,求无序三元组 (a,b,c) ( a , b , c ) 使得 a|A,b|B,c|C a | A , b | B , c | C ,一共 T T 组数据,T,A,B,C105

题解

官方题解里一堆长难句和生词,我看不懂 QwQ Q w Q
在网上看到一个清奇的思路,分享一下
直接容斥的话很麻烦,一不小心可能就会 WA W A 掉了,传统的容斥思路实质上是强制规定了第一个位置填 A A 的约数,第二个位置强行填B的约数,第三个位置强行填 C C 的约数,这样带来的问题就是一个数的约数可能也是另一个数的约数,再考虑上约数和约数可能相等,这个问题就变得异常复杂,其实是因为三个位置填的数之间的联系太复杂。
如果能按照某种划分标准,把所有的约数划分成三个集合,使得我从每个集合中取出一个数就能满足这三个数分别是A,B,C的约数,而且集合与集合之间没有交集,那么这个问题就一下子简单很多。
我用 1...7 1...7 的二进制表示状态,第一位为 1 1 说明它是A的约数,第二位为 1 1 说明它是B的约数,第三位为 1 1 说明它是C的约数
显然这 7 7 个集合任意选出几个数字,都不会有重复
我只要枚举集合编号(i,j,k),然后 check c h e c k 一下我选出来的这些数能不能和 A,B,C A , B , C 一一对应起来,如果能对应起来,我就把 card(i)×card(j)×card(k) c a r d ( i ) × c a r d ( j ) × c a r d ( k ) 计入答案,如果 i=j i = j ,就把 C2card(i)+21×card(k) C c a r d ( i ) + 2 − 1 2 × c a r d ( k ) 计入答案,如果 i=j=k i = j = k 我就把 C3card(i)+31 C c a r d ( i ) + 3 − 1 3 ,其它情况也是一样。
其中 card() c a r d ( ) 表示有限集合的元素数量
Crn+r1 C n + r − 1 r 是从 n n 个元素里允许重复地选择r个元素的方案数

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
#define clear(x) memset(x,0,sizeof(x))
#define maxn 100010
using namespace std;
ll cnt[8], T, d[maxn], now[4];
bool ok[8][8][8];
ll read(ll x=0)
{
    char f=1, c;
    for(c=getchar();c<48 or c>57;c=getchar())if(c=='-')f=-1;
    for(;c>=48 and c<=57;c=getchar())x=(x<<1)+(x<<3)+c-48;
    return x;
}
ll C(ll n, ll m)
{
    if(n==0)return 0;
    ll ans=1, i;
    for(i=n;i>n-m;i--)ans*=i;
    for(i=1;i<=m;i++)ans/=i;
    return ans;
}
void calc_d()
{
    ll i, j;
    for(i=1;i<maxn;i++)for(j=i;j<maxn;j+=i)d[j]++;
}
bool dfs(ll pos)
{
    ll i, t;
    if(pos>4)return true;
    for(i=1;i<=3;i++)if(pos&now[i])
    {
        t=now[i], now[i]=0;
        if(dfs(pos<<1))return true;
        now[i]=t;
    }
    return false;
}
void calc_ok()
{
    ll i, j, k;
    for(i=1;i<8;i++)for(j=i;j<8;j++)for(k=j;k<8;k++)
    {
        now[1]=i, now[2]=j, now[3]=k;
        ok[i][j][k]=dfs(1);
    }
}
ll gcd(ll a, ll b){return !b?a:gcd(b,a%b);}
void calc_cnt(ll a, ll b, ll c)
{
    clear(cnt);
    ll ab=gcd(a,b), ac=gcd(a,c), bc=gcd(b,c), abc=gcd(ab,c);
    cnt[1]=d[c]-d[ac]-d[bc]+d[abc];     //001
    cnt[2]=d[b]-d[ab]-d[bc]+d[abc];     //010
    cnt[3]=d[bc]-d[abc];                //011
    cnt[4]=d[a]-d[ab]-d[ac]+d[abc];     //100
    cnt[5]=d[ac]-d[abc];                //101
    cnt[6]=d[ab]-d[abc];                //110
    cnt[7]=d[abc];                      //111
}
void solve(ll a, ll b, ll c)
{
    ll i, j, k, ans=0;
    calc_cnt(a,b,c);
    for(i=1;i<8;i++)for(j=i;j<8;j++)for(k=j;k<8;k++)if(ok[i][j][k])
    {
        if(i==j and j==k)ans+=C(cnt[i]+2,3);
        else if(i==j and j!=k)ans+=C(cnt[i]+1,2)*C(cnt[k],1);
        else if(i!=j and j==k)ans+=C(cnt[i],1)*C(cnt[j]+1,2);
        else ans+=C(cnt[i],1)*C(cnt[j],1)*C(cnt[k],1);
    }
    printf("%I64d\n",ans);
}
int main()
{
    ll a, b, c;
    calc_d();
    calc_ok();
    for(T=read();T;T--)
    {
        a=read(), b=read(), c=read();
        solve(a,b,c);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值