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

链接

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

大意

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

题解

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

猜你喜欢

转载自blog.csdn.net/fsahfgsadhsakndas/article/details/81053620