HDU 2841 Visible Trees(容斥原理)

传送门

Visible Trees

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2404    Accepted Submission(s): 1002


Problem Description
There are many trees forming a m * n grid, the grid starts from (1,1). Farmer Sherlock is standing at (0,0) point. He wonders how many trees he can see.

If two trees and Sherlock are in one line, Farmer Sherlock can only see the tree nearest to him.
 

Input
The first line contains one integer t, represents the number of test cases. Then there are multiple test cases. For each test case there is one line containing two integers m and n(1 ≤ m, n ≤ 100000)
 

Output
For each test case output one line represents the number of trees Farmer Sherlock can see.
 

Sample Input
  
  
2 1 1 2 3
 

Sample Output
  
  
1 5
 

Source
 
题目大意:

给定一个 m * n 的方格,左下角的点为(1,1),右上角的点(n,m),然后每个方格都是一棵树,然后一个人站在(0,0)点,往森林里看,当两棵树在一条线上的时候,它只能看到最前面的那个点,后面的树就不计了,计算能够看到多少颗树。


解题思路:

我们通过观察可以发现当(x,y)的GCD不是1的时候就不用计算,假设(x,y)和(x1,y1),必有 x/x1 ==  y/y1;

GCD(x,y)== x/x1 == y/y1 != 1;所以我们要求的就是在 1 - n中每个数 与 1 - m中互素的个数之和,但是呢,这样考虑如果用GCD来算的话,肯定会超时,所以我们才用它的对立面,也就是容斥原理来算。我们可以考虑1与[1,m]里面多少个数互质,2与[1,m]里面多少个数互质,3与[1,m]里面多少个数互质……n与[1,m]里面多少个数互质,把这些结果全部累加起来即可我们先算x在1 - m区间中互素的个数,那么我们要是用容斥原理的话,那么就是求在1 - m区间内与 x 不互素的个数,那么我们要求1 - m 中与 x 有一个素因子的个数,有两个素因子的个数……,然后满足奇家偶减,有奇数个素因子的数加,偶数个的减。最后得到的就是在 1 - m区间中与x不互素的个数,用n减掉,然后再1-n循环跑一遍就行,我用两个代码 一个是直接用 dfs 容斥的(这个不是很理解),还有一个就是 二进制枚举的(个人比较推荐这个 容易理解)

My Code:(二进制枚举)

/**
id:ITAK
Exe.Time:62ms
Exe.Memory:5880k
**/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 1e5+5;
typedef long long LL;
/**
prime[i][j] 表示对 i 进行素因子分解,素因子存在 j 中
**/
int prime[MAXN][10];
int cnt[MAXN];///素因子的种类数
void Init()
{
    memset(cnt, 0,sizeof(cnt));
    for(int i=2; i<MAXN; i++)
    {
        if(cnt[i])
            continue;
        prime[i][0] = i;
        cnt[i] = 1;
        for(int j=2; j*i<MAXN; j++)
            prime[j*i][cnt[j*i]++] = i;
    }
}
LL RongChi(int m, int n)
{
    LL ret = 0;
    for(int i=1; i<(1<<cnt[m]); i++)
    {
        int sum = 0;
        LL ans = 1;
        for(int j=0; j<cnt[m]; j++)
        {
            if(i & (1<<j))
            {
                sum++;
                ans *= prime[m][j];
            }
        }
        if(sum & 1)
            ret += n/ans;
        else
            ret -= n/ans;
    }
    return n-ret;
}
int main()
{
    ///cout<<2*3*5*7*11*13*17*19<<endl;
    Init();
    /**
    for(int i=0; i<20; i++)
    {
        for(int j=0; j<5; j++)
            cout<<prime[i][j]<<' ';
        cout<<endl;
    }**/
    int T, n, m;
    cin>>T;
    while(T--)
    {
        cin>>m>>n;
        if(m < n)
            swap(m, n);
        LL ret = m;///i==1时 互素个数肯定是m
        for(int i=2; i<=n; i++)
            ret += RongChi(i,m);
        printf("%I64d\n",ret);
    }
    return 0;
}


My Code:(dfs)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 1e5+5;
typedef long long LL;
/**
prime[i][j] 表示对 i 进行素因子分解,素因子存在 j 中
**/
int prime[MAXN][10];
int cnt[MAXN];///素因子的种类数
void Init()
{
    memset(cnt, 0,sizeof(cnt));
    for(int i=2; i<MAXN; i++)
    {
        if(cnt[i])
            continue;
        prime[i][0] = i;
        cnt[i] = 1;
        for(int j=2; j*i<MAXN; j++)
            prime[j*i][cnt[j*i]++] = i;
    }
}
LL RongChi(int m, int n, int i)
{
    LL ret = 0;
    for(int j=i; j<cnt[m]; j++)
        ret += n/prime[m][j]-RongChi(m,n/prime[m][j],j+1);
    return ret;
}
int main()
{
    ///cout<<2*3*5*7*11*13*17*19<<endl;
    Init();
    /**
    for(int i=0; i<20; i++)
    {
        for(int j=0; j<5; j++)
            cout<<prime[i][j]<<' ';
        cout<<endl;
    }**/
    int T, n, m;
    cin>>T;
    while(T--)
    {
        cin>>m>>n;
        if(m < n)
            swap(m, n);
        LL ret = m;
        for(int i=2; i<=n; i++)
            ret+= m-RongChi(i,m,0);
        printf("%I64d\n",ret);
    }
    return 0;
}



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值