day2啦
130/300 小开心 qwq
人性翻译:给定一串数字,把这串数字重新排列,使得这串数字的
第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
考场爆零,数论渣渣真的绝望
转化一下问题,我们令\(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;
}
线段树,先不搞
Soultion:
总结:
再也不敢说自己数论只会gcd了!