国庆长乐酱油之旅day2

day2啦

130/300 小开心 qwq

T1:最大公约数

人性翻译:给定一串数字,把这串数字重新排列,使得这串数字的

第1个数字的公约数+第一个和第二个数的公约数+第一个数和第二个数和第三个数的最大公约数+.....+整串数字的最大公约数之和

最大!!


首先拿到这道题, 我们可以知道每一次求出的公约数是单调不递增的,
且求这个公约数是可以递推实现的 
for example: 
假设这串数字的顺序为:2 4 9 5 5
设f[i]表示前i项的最大公约数
则可得
f[1]=2;
f[2]=gcd(f[1],4)=2;
f[3]=gcd(f[2],9)=1;
f[4]=gcd(f[3],5)=1;
f[5]=gcd(f[4],5)=1;
则ans=f[1]+f[2]+f[3]+f[4]+f[5]=7 

根据这个性质

我们就要让它求出下一个公约数的时候尽量让公约数不发生改变,
即如果原来之前所有项的公约数是5,那我们就要尽可能让这个5不发生改变,
因为这个公约数序列是单调不递增的,所以这样做才能达到决策最优

那么什么时候公约数才不会发生改变呢?

自然是当 目前选的这个数是之前的公约数的倍数时,更新后的公约数大小不变,即之前公约数是5,那如果现在让10跟在后面,那更新后的公约数任然是5。

上代码

Code:

#include<iostream>
#include<cstdio>
#include<cstring>
#define size 100010

using namespace std;

int t,n,maxa;
long long ans;
long long num[size],used[size],sum[size];
//num[i]储存n个数中,是i的倍数的个数
//used[i]存储n个数中,数字i出现的次数
//sum[i]代表填完了所有i的倍数的数字后得到的最大值 

void start()
{
    freopen("gcd.in","r",stdin);
    freopen("gcd.out","w",stdout);
}

void end()
{
    fclose(stdin);
    fclose(stdout); 
} 

//求两数最大公约数 
int gcd(int a, int b)
{
    return b==0 ? a : gcd(b, a % b);
}

int main()
{
    start();
    
    scanf("%d",&t);
    while(t--)
    {
        maxa=0;
        ans=0;
        memset(num,0,sizeof(num));
        memset(used,0,sizeof(used));
        memset(sum,0,sizeof(sum));
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            used[x]++;//记录每个数出现的次数 
            maxa=max(maxa,x);//找到出现的最大的数 
        }
        for(int i=maxa;i>=1;i--)//从出现的最大数往回推 
        {
            for(int j=i;j<=maxa;j+=i)//枚举i的倍数 
                num[i]+=used[j];//累计i的倍数的个数 
            sum[i]=num[i]*i;//初始化 
        }
        for(int i=1;i<=maxa;i++)
        //枚举先放的数 
        {
            for(int j=i;j<=maxa;j+=i)//枚举i的倍数
                sum[j]=max(sum[j],sum[i]+num[j]*(j-i));
                //sum[j]的值就等于sum[i]后面接上一段j的倍数的数字能得到的最大值 
            ans=max(ans,sum[i]);
            //处理最终答案 
        }
        printf("%lld\n",ans);
    }
    
    end();
    
    return 0;
}
//By Yfengzi 

T2:假的快速傅里叶变换

考场爆零,数论渣渣真的绝望

转化一下问题,我们令\(g[i]\)表示满足\((a\times b) | i\)的有序正整数对\((a ,b)\)的个数。

\(\sum_{i=1}^n \sum_{d|i}f[d]= \sum_{i=1}^ng[i]\)

因为对于一个\(i\)的约数\(d\),都有一个 \(\bf\frac{i}{d}\) 的约数与之对应,并且\(d=\frac{i}{d}\)当时,正整数对只会计算一次。

思考一下\(\sum_{i=1}^ng[i]\)的实际意义,即对\(abc<n\)满足的正整数\(a,b,c\)所能组成所有有序数对的个数

考虑\(a,b\)枚举得到\(c\)的范围计算答案(就是这么暴力),直接枚举好像还是\(O(n^2)\)

不妨限制下的\(a,b,c\)大小关系,即令\(a<b<c\),最后乘上6,这样枚举的复杂度大概是\(O(\sqrt{n})\)。相等的情况单独枚举,复杂度\(O(\sqrt{n})\)

Code:

#include<iostream>
#include<cstdio>

using namespace std;

long long  n,ans;
int main()
{
    scanf("%lld",&n);
    for(long long a=1;a*a*a<=n;a++)//枚举a 
    {
        for(long long b=a+1;a*b*b<=n;b++)//枚举b 
        {
            long long c=n/b/a;//计算c 
            if(c>b) ans+=(c-b)*6;//如果符合满足a,b,c的大小关系,就计算 
        }
    }
    
    //处理a=b的情况 
    for(long long a=1;a*a<=n;a++)
    {
        long long c=n/a/a;//计算c 
        ans+=c*3;//*3,因为对于两个不同的数据,只有三种情况 
        if(c>=a) ans-=2;//处理a=b=c的情况,如果c>=a,则范围内,必有c=a,则只有一种情况 
    }
    
    printf("%lld",ans);
    
    return 0;
}

T3:Orz膜拜

线段树,先不搞

Soultion:

总结:

再也不敢说自己数论只会gcd了!

猜你喜欢

转载自www.cnblogs.com/fengzi8615/p/9758259.html
今日推荐