2017.10.30清北学堂测试

Problem 1 :a

这里写图片描述
这里写图片描述

【思路】
不得不说本题正解还是比较难以理解的,还请仔细阅读推算。
(a * b)| x 意为(a * b)整除x,这里a,b限制条件为1<=a,b<=n,则其答案数与x整除(a * b)的答案数相同。

对于30%的数据,O(n^3)暴力:
枚举a,b,和1-n,判断是否存在一个c使得x整除(a * b)

对于60%的数据,O(n^2)暴力:——这是正解的基础
我们设a * b * c <= n,a * b * c1 = n此处的c1有两层含义:
1. a * b作为1-n中的答案的数量
2.a * b * c<=n的c中最大的c
例:n = 7
1 * 2 * 1
1 * 2 * 2
1 * 2 * 3
1 * 2 * 4
……
则c1 = 3,由于答案是对于1-n界定的,则1 * 2 * 1,1 * 2 * 2,1 * 2 * 3必定可以在其中找到对于的数,即作为1-n中的答案而存在。
那么我们就可以快速确定一组a b对答案数的贡献啦不用枚举1-n啦~
枚举a,b,n/(a * b)即为(a * b)在1—n中作为答案的数量,累加即可。
代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ri register int
using namespace std;
int n,ans;
int main()
{
    scanf("%d",&n);
    for(ri i=1;i<=n;++i)
    for(ri j=1;j<=n;++j)
    {
        if(i*j<=n)
        {
            int sum=i*j;
            ans+=n/sum;
        }
    }
    printf("%d",ans);
    return 0;
}

对于100%的数据,O(n^(2/3))暴力:
我们设a<=b<=c(这个设定并不会影响答案,因为我们可以交换a b c的值,这样做是为了用一组递增的a b c代表其不同组合顺序对答案的贡献),则1<=a<=n^(1/3)——这是我们枚举a的范围。
那么我们可以分情况讨论:
对于一组a,b,c
1.若a = b = c,则a,b,c对答案数的贡献为1。
例:1 1 1——对于样例
2.若a = b,则a,b,c的全排列(这个定义好像不准确…因为全排列是不同的元素..求指正)对答案数的贡献为3,b = c,a = c所贡献的答案与之重复则不计入答案数。
例:1 1 6,1 6 1,6 1 1
3.若a!=b!=c,则a,b,c的全排列对答案数的贡献为6。
例:1 2 3,1 3 2,2 3 1,2 1 3,3 1 2,3 2 1
可以发现的是,我们设定的a<=b<=c中的=号只对情况1,2生效
分情况统计答案数,注意在实现过程中尽量减少不必要的枚举
代码解释——两处难点很需要解释
第一处:
我们now现在加上了一个c1,为当前a=b在1-n中的数量(注意我们现在强调的是c的含义1),此时的c1是有可能包含一个情况1的次数的,且至多包含一个,因为我们的a,b已经确定了。
我们假定对于当前的a = b,有一个c2 = a即有一个情况1混在c中。
设这个答案对应数为x1,则有a * a * a = x1。
我们的判断条件是:若存在a * a * a <= n,则…
如果条件成立,那么x1一定满足1<=x1<=n,那么对a=b情况为答案的统计中一定包含了一个a=b=c,即一定将对应数为x1的情况统计了。
理解这个要注意c1的含义1,并通过例子理解。
例:n = 20
2 * 2 * 1
2 * 2 * 2
2 * 2 * 3
2 * 2 * 4
2 * 2 * 5
……
此时c1=5,1<=x1<=n且x1为2*2的倍数,列出a b c后,x1一定在n的上方,一定会被统计在c1里。
那么我们就应该将now中多加的一个情况1减去,直接将其对答案的贡献(1)加入答案
如果条件不成立意为x1没有被统计,c1只包含了情况2,放心加入即可。
第二处
首先我们的枚举可以保证a,b不相同。
此处的n/(i*j)表示当a=i,b=j时最后一个满足a * b * c<=n的c1,表示序号(注意我们现在强调的是c的含义2)减去j表示仅计算c的序号大于j的情况数。这里减去的j数值同当前的b,但其意义为——数值与b相等的c!那么对于c1小于等于b的情况,我们都已经在之前的枚举中(枚举a=b,a!=b)统计过了——
原因为:
对于一组a,b,c,c<=b的情况:
1.若c

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
LL n,ans=0,now=0;
int main()
{
    scanf("%lld",&n);   
    for(LL i=1;(i*i)<=n;i++)//a与b相同枚举其中之一 
    {
        now+=n/(i*i);//在1-n中作为答案的数量 
        LL s=n/i;
        if(i*i<=s)//若成立则必定有一个数1<=x1<=x,使得a*a*a=x1
        {//---第一处
            ans++;//每个对答案的贡献为1 
            now--;
        } 
    }
    ans+=now*3,now=0;//每个对答案的贡献为3 
    for(LL i=1;(i*i)<=(n/i);i++)//枚举a
    {
        LL s=n/i;-
        for(LL j=i+1;(j*j)<=s;j++)//枚举b,a b必定不相同1 
        now+=n/(i*j)-j;//统计之前未被统计的a b c不同的数量---第二处
    }
    ans+=6*now;//每个对答案数的贡献为6 
    printf("%I64d\n",ans);
    return 0;
}

为答案选取制定方便统计又不影响正确性的限制条件,真是智慧的技巧~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值