Codeforces Round #641 Div2.
官方题解:https://www.luogu.com.cn/blog/Caro23333/codeforces-round-641-zhong-wen-ti-xie
小记:
和队友一起打比赛,探讨题目,exciting!
比赛中出现的问题:
-
A题 想的太复杂,一看到因数,就想用素数分解。
-
B题看错题目,剩下一个小时才在wa了好几发下知道看错了。
-
还是手生了,迭代器和set都不会用了。
收获:
- C题前后缀求gcd
- 调和级数估算复杂度 1 1 + 1 2 + 1 3 + . . . + 1 n ≈ l n n \frac{1}{1}+\frac{1}{2}+\frac{1}{3}+...+\frac{1}{n}\approx ln\,n 11+21+31+...+n1≈lnn
赛后补题的wa点:
- 以为看数据不会超过int,其实在过程中,或者经过计算后,变超出int,要使用long long
- 细节还是没有想对,想清楚,比如C题的第<二> wa在第6点,看别人的题解后才明白自己那个细节没有想清楚。
A题
#include "bits/stdc++.h"
#define ll long long
using namespace std;
int main()
{
int cas;
scanf("%d",&cas);
while (cas--)
{
ll n,k;
scanf("%lld %lld",&n,&k);
for(int i=1;i<=k;++i)
{
if(n%2==0){n+=k*2;
break;}
for(int d=3;d<=n;++d)
if(n%d==0){
n+=d;--k;
break;
}
}
printf("%lld\n",n);
}
}
B题
<一>枚举质因数分解
ps:赛场上想的是把质数求出来,然后再把i 一个个进行质因数分解,而得到质因数之后再一个个组合得到因数,显然是 此举变成负优化,实际上,求质因数可以直接枚举,在 O ( n ) O(\sqrt{n}) O(n) 的时间内就可以完成
#include "cstdio"
#include "bits/stdc++.h"
#define ll long long int
using namespace std;
const int maxn=1e5+10;
int all[maxn];
int rak[maxn];
int main()
{
// freopen("in.text","r",stdin);
int cas,n;
scanf("%d",&cas);
while (cas--)
{
int ans=0;
memset(rak,0,sizeof(rak));
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&all[i]);
for(int d=1;d*d<=i;++d)
{
if(i%d!=0)continue;
if(all[i]>all[d])rak[i]=max(rak[i],rak[d]+1);
if(all[i]>all[i/d])rak[i]=max(rak[i],rak[i/d]+1);
}
ans=max(ans,rak[i]);
}
printf("%d\n",ans+1);
}
return 0;
}
<二> 倍数转移
时间复杂度为
O
(
s
l
o
g
s
)
O(s\,log\,s)
O(slogs)
因
为
1
1
+
1
2
+
1
3
+
.
.
.
+
1
n
≈
∫
i
=
1
i
=
n
1
x
d
x
l
n
x
∣
i
=
1
i
=
n
=
l
n
n
因为 \frac{1}{1}+\frac{1}{2}+\frac{1}{3}+...+\frac{1}{n}\approx \int_{i=1}^{i=n}\frac{1}{x}dx ln x|_{i=1}^{i=n}=ln\,n\\
因为11+21+31+...+n1≈∫i=1i=nx1dxlnx∣i=1i=n=lnn
#include "bits/stdc++.h"
using namespace std;
const int maxn=1e5+10;
int s[maxn];
int rak[maxn];
int main()
{
// freopen("in.text","r",stdin);
int cas;
scanf("%d",&cas);
while (cas--)
{
int n;
int ans=0;
memset(rak,0,sizeof(rak));
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&s[i]);
for(int i=1;i<=n;++i) {
for (int d = i*2; d <= n; d += i)
if (s[d] > s[i])rak[d] = max(rak[d], rak[i] + 1);
ans=max(ans,rak[i]);
}
printf("%d\n",ans+1);
}
return 0;
}
C题
官方题解:
首先 令p为质数,k为任意正数,ans为要求的答案, a i a_i ai如题中意,
如果 p k ∣ a n s p^k|ans pk∣ans,则至少有n-1个 a i a_i ai,满足 p k ∣ a i p^k|a^i pk∣ai。
因为 l c m ( a i , a j ) lcm({a_i,a_j}) lcm(ai,aj) 得到的集合 包含任意两个数的最小公倍数
<一> 对每n-1个数求gcd,
得到n个数后,再求这n个数的lcm,就是答案。
那么要如何求n-1个数的gcd?用前后缀维护就可以快速求出,代码见官方题解
🐤注意点:
- 那个ans要用long long,否则会wa在第9个点
- 不要试
a,b=b%a,a;
,这种写法会error
#include "bits/stdc++.h"
#define ll long long int
using namespace std;
const int maxn=1e5+10;
int a[maxn];
int gcd(int a,int b)
{
while (a)
{
int tem=a;
a=b%a;
b=tem;
}
return b;
}
int pre[maxn];
int suf[maxn];
int main()
{
// freopen("in.text","r",stdin);
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
pre[1]=a[1];
suf[n]=a[n];
for(int i=2;i<=n;++i)pre[i]=gcd(pre[i-1],a[i]);
for(int i=n-1;i>=1;--i)suf[i]=gcd(suf[i+1],a[i]);
ll ans=1;
for(int now,i=1;i<=n;++i)
{
if(i==1)now=suf[i+1];
else if(i==n)now=pre[i-1];
else now=gcd(pre[i-1],suf[i+1]);
ans=now*ans/gcd(now,ans);
}
printf("%lld\n",ans);
return 0;
}
<二> 枚举n个数 找出质因数的次小k
对每个数进行分解,更新质数的最小k和 次小k。
但是这样不能做优化(即不能中断),时间效率很低,会tle在 第11上。
犯错点:
- 细节没有想清楚,所以一直wa在第6
如果n个数都有p,那么k最小的两个数的公倍数中为次小k,
如果n-1个数都有p,那么k最小的和没有p的 这两个数组合成的公倍数就为最小k。
- 第二个是wa在第9个点上,答案是求公倍数的公因数,因此可能超出int,要使用long long
- 这个是tle在第11个点上,这是这个思路的硬伤,不能做中断优化,只能执行到底,而<三>可以解决这个问题
//该代码会tle
#include "bits/stdc++.h"
#define ll long long
using namespace std;
const int MAXN=1e5+10;
const int BiG=2e5+10;
int a[MAXN];
int kMin[BiG],kMinSec[BiG],kCnt[BiG];
bool noPrim[BiG];
int prim[MAXN],cntPrim=0;
void MakePrim()
{
for(int i=2;i<BiG;++i)
if(noPrim[i]==0){
prim[++cntPrim]=i;
for(int d=i+i;d<BiG;d+=i)noPrim[d]=1;
}
}
void Give(int k,int p)
{
if(kMin[p]<=k){
if(kMinSec[p]>k)kMinSec[p]=k;
}
if(kMin[p]>=k){
kMinSec[p]=kMin[p];
kMin[p]=k;
}
}
int main()
{
// freopen("in.text","r",stdin);
MakePrim();
memset(kMin,0x3f3f3f3f,sizeof(kMin));
memset(kMinSec,0x3f3f3f3f,sizeof(kMinSec));
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
for(int i=1;i<=n;++i)
{
int cnt=0;
while (a[i]>=prim[++cnt])
{
int p=prim[cnt];
if(a[i]%p==0)
{
int k=0;
++kCnt[p];
while (a[i]%p==0){++k;a[i]/=p;}
Give(k,p);
}
}
}
ll ans=1;
for(int i=1;i<cntPrim;++i)
{
int p=prim[i];
if(kCnt[p]==(n))
{
while (kMinSec[p]--){
ans *= p;
}
}
else if(kCnt[p]==n-1)
while (kMin[p]--)ans*=p;
}
printf("%lld\n",ans);
return 0;
}
<三> 枚举质数找出质因数的次小k
这个就是官方题解的思路了
我是在<二>的基础上改过来的,会犯的错误也和上一样
#include "bits/stdc++.h"
#define ll long long
using namespace std;
const int MAXN=1e5+10;
const int BiG=2e5+10;
int a[MAXN];
int kMin[BiG],kMinSec[BiG],kCnt[BiG];
bool noPrim[BiG];
int prim[MAXN],cntPrim=0;
void MakePrim()
{
for(int i=2;i<BiG;++i)
if(noPrim[i]==0){
prim[++cntPrim]=i;
for(int d=i+i;d<BiG;d+=i)noPrim[d]=1;
}
}
void Give(int k,int p)
{
if(kMin[p]<k){
if(kMinSec[p]>k)kMinSec[p]=k;
}
if(kMin[p]>=k){
kMinSec[p]=kMin[p];
kMin[p]=k;
}
}
int main()
{
// freopen("in.text","r",stdin);
MakePrim();
memset(kMin,0x3f3f3f3f,sizeof(kMin));
memset(kMinSec,0x3f3f3f3f,sizeof(kMinSec));
int n;
ll ans=1;
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=cntPrim;++i)
{
if(prim[i]>a[2])break;
int d,fau=0;
for(d=1;d<=n;++d)
if(a[d]%prim[i]==0){
int k=0,tem=a[d];
while (tem%prim[i]==0){
tem/=prim[i];++k;
}
Give(k,i);
}else {
++fau;
if(fau>=2)break;
}
if(fau>=2)continue;
if(d>=(n+1))
{
if(fau==0)while (kMinSec[i]--)ans*=prim[i];
if(fau==1)while (kMin[i]--)ans*=prim[i];
}
}
printf("%lld\n",ans);
return 0;
}