A 因数个数和
链接:https://www.nowcoder.com/acm/contest/158/A
来源:牛客网
题目描述
q次询问,每次给一个x,问1到x的因数个数的和。
输入描述:
第一行一个正整数q ;
接下来q行,每行一个正整数 x
输出描述:
共q行,每行一个正整数表示答案
示例1
输入
4
1
2
3
10
输出
1
3
5
27
说明
1的因数有1
2的因数有1,2
3的因数有1,3
以此类推
备注:
1<=q<=10 ,1<= x<=109
这个题有两种方法做。
我们现在例如n=12.
1:1
2:1,2
3:1,3
4:1,2,4
5:1,5
6:1,2,3,6
7:1,7
8:1,2,4,8
9:1,3,9
10:1,2,5,10
11:1,11
12:1,2,3,4,6,12
首先第一种方法:我们可以看到求因子个数,其实就是求因子出现的次数。通过上面因子数可以看出,1出现12次,2出现5次,3出现4次……。我们可以观察到因子数的和就是 n/1+n/2+n/3……+n/n。这是一个y=n/x的函数。可以在草稿纸上画出图形,图形是关于y=x对称的。并且对称点在sqrt(n)处,然后我们求处这个函数1到n的离散值就行了,求和相当于求面积,因为对称点在sqrt(n)处,所以我们只用求到sqrt(n)处再乘以2,但是这样有加重复的,所以我们要减去中间加重复的矩形,具体思想看代码。
![](/qrcode.jpg)
#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>
const int maxn=1e4+10;
const int mod=1e9+7;
const int inf=1e8;
#define me(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int main()
{
int t;cin>>t;
while(t--)
{
int n;scanf("%d",&n);
ll sum=0;
for(int i=1;i*i<=n;i++)
sum+=n/i;
ll temp=sqrt(n);
printf("%lld\n",sum*2-temp*temp);
}
return 0;
}
第二种方法是利用算数的基本原理,我们设某个数的因子数的函数为T(x)。所以 ,ans=T(1)+T(2)+……+T(12)。然后我们看每个函数是有规律可找的。
ans=12∗1+6∗2+4∗3+3∗4+2∗5+2∗6+1∗7+1∗8+1∗9+1∗10+1∗11+1∗12。ans=∑ni=1n/i∗i。(n/i)是要整除。
可以整理得:ans=12∗1+6∗2+4∗3+3∗4+2∗(5+6)+1∗(7+8+9+10+11+12)。
则对于每一个 n/i 都有一个范围
n/i 范围[l,r]
当前n/i 在范围内 对ans的贡献是
12 [1,1] 12*1
6 [2,2] 6*2
4 [3,3] 4*3
3 [4,4] 3*4
2 [5,6] 2*(5+6)
1 [7,12] 1*(7+8+……+12) 然后这里可以等差数列求和
可以发现 每一个l等于上一个r+1 而r=n/(n/l)这里是整除
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL X(LL n)
{
LL l, r;
LL ans = 0;
for( l = 1; l <= n; l = r+1)
{
r = n/(n/l);
ans += n/l * (r-l + 1);
//(r-l+1) 项数
//(n/l) 是拥有这样因子的区间个数
}
return ans;
}
int main()
{
int t;
cin>>t;
while(t--)
{
LL x;
cin>> x;
printf("%lld\n",X(x));
}
}
B 最长区间
链接:https://www.nowcoder.com/acm/contest/158/B
来源:牛客网
题目描述
给你一个长度为 n 的序列 a ,求最长的连续的严格上升区间的长度。
同时会进行 m 次修改,给定 x , y ,表示将 ax 修改为 y ,每次修改之后都要求输出答案。
输入描述:
第一行 2 个数 n,m,表示序列长度,修改次数; 接下来一行 n 个数表示 ; 接下来 m 行,每行 2 个数 x , y ,描述一次修改。
输出描述:
第一行 1 个数表示最初的答案; 接下来 m 行,第 i 行 1 个数表示第 i 次修改后的答案。
示例1
输入
4 3 1 2 3 4 3 1 2 5 3 7
输出
4 2 2 3
说明
序列变换如下: 1 2 3 4 1 2 1 4 1 5 1 4 1 5 7 4
备注:
n,m ≤ 100000,1 ≤ x ≤ n,1 ≤ ai,y ≤ 100
PS:听说暴力能过,但是我这里写的是线段树,大家可以参考下。
#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>
#include<cmath>
#include<vector>
const int maxn=1e5+5;
const int mod=1e9+7;
#define me(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int a[maxn],num[maxn<<2],lnum[maxn<<2],rnum[maxn<<2];
void updata(int l,int r,int rt)///更新区间最长子序列长度
{
int m=(l+r)/2;
lnum[rt]=lnum[rt<<1];
rnum[rt]=rnum[rt<<1|1];
num[rt]=max(num[rt<<1],num[rt<<1|1]);
if(a[m]<a[m+1])
{
if(lnum[rt<<1]==m-l+1)
lnum[rt]=lnum[rt<<1]+lnum[rt<<1|1];
if(rnum[rt<<1|1]==r-m)
rnum[rt]=rnum[rt<<1|1]+rnum[rt<<1];
num[rt]=max(num[rt],lnum[rt<<1|1]+rnum[rt<<1]);
}
}
void build(int l,int r,int rt)///建树
{
if(l==r)
{
num[rt]=lnum[rt]=rnum[rt]=1;
return ;
}
int m=(l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
updata(l,r,rt);
}
void pushdata(int s,int c,int l,int r,int rt)///更改节点值
{
if(l==r)
{
a[l]=c;
return ;
}
int m=(l+r)>>1;
if(s<=m)
pushdata(s,c,l,m,rt<<1);
else
pushdata(s,c,m+1,r,rt<<1|1);
updata(l,r,rt);
}
int query(int L,int R,int l,int r,int rt)///求出最长子序列长度
{
if(l>=L&&R>=r)
return num[rt];
int ret=0;
int m=(l+r)/2;
if(m>=L)
ret=max(ret,query(L,R,l,m,rt<<1));
if(R>m)
ret=max(ret,query(L,R,m+1,r,rt<<1|1));
if(a[m]<a[m+1])
ret=max(ret,min(m-L+1,rnum[rt<<1])+min(R-m,lnum[rt<<1|1]));///[m+1,R]与[m+1,r]相交部分的最大前缀+[L,m]与[l,m]的最大后缀.
return ret;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
build(1,n,1);
cout<<query(1,n,1,n,1)<<endl;
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
pushdata(a,b,1,n,1);
printf("%d\n",query(1,n,1,n,1));
}
return 0;
}