题解 P4963 【美樱的颜料】
一、朴素做法
先考虑确定了选择的第一个数
所以需要预处理的是
令
(假设
总贡献
若
然后只要暴力枚举选的第一个数即可
直接这样做空间会炸.但是在讲正解前还是看一下这个做法的参考代码吧:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=10000010;
int n,m,p[N],f[N],tot,ans;
bool np[N];
int main()
{
int i,j,temp;
cin>>n>>m;
for (i=2;i<=n;++i)
{
if (!np[i])
{
p[++tot]=i;
f[i]=1;
}
for (j=1;j<=tot&&i*p[j]<=n;++j)
{
np[i*p[j]]=true;
f[i*p[j]]=i;
if (i%p[j]==0)
{
break;
}
}
}
for (i=1;i<=n;++i)
{
temp=0;
for (j=i;;j=f[j])
{
if (n/j<m)
{
temp+=n/j*(j-f[j]);
}
else
{
temp+=m*j;
break;
}
}
ans=max(ans,temp);
}
cout<<ans;
return 0;
}
这个做法的时间复杂度是
二、优秀做法
可以发现,
注意计算
for (i=2;i<=n;++i)
{
if (!np[i])
{
p[++tot]=i;
f[i]=1;
}
for (j=1;j<=tot&&i*p[j]<=n;++j)
{
np[i*p[j]]=true;
f[i*p[j]]=i;
if (i%p[j]==0)
{
break;
}
}
}
事实上我们虽然不能快速地求出任意一个
先考虑
若
总的时间复杂度是
参考代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int N=10000010;
void dfs(int u,int fa,int sum);
int n,m,p[N/10],tot,ans;
bool np[N];
int main()
{
int i,j;
cin>>n>>m;
for (i=2;i<=n;++i)
{
if (!np[i])
{
p[++tot]=i;
}
for (j=1;j<=tot&&i*p[j]<=n;++j)
{
np[i*p[j]]=true;
if (i%p[j]==0)
{
break;
}
}
}
dfs(1,0,0);
cout<<ans;
return 0;
}
void dfs(int u,int fa,int sum)
{
sum+=min(m,n/u)*(u-fa);
ans=max(ans,sum);
for (int i=1;i<=tot&&u*p[i]<=n;++i)
{
int v=u*p[i];
if (n/v>=m)
{
dfs(v,0,0);
}
else
{
dfs(v,u,sum);
}
if (u%p[i]==0)
{
break;
}
}
}
三、优秀的dp做法和优化的朴素做法
这两种方法的核心思想是:最优的起点一定大于等于
所以,可以得到下面的两种做法,压缩空间的核心都是只存一半,另一半直接更新答案而不保存,就不详细讲解了:
跑的飞快的dp做法:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=10000010;
int n,m,p[N/10],f[N/2],tot,ans;
int main()
{
int i,j,v;
cin>>n>>m;
ans=max(m,n+m-1);
f[1]=m;
for (i=2;i<=n/2;++i)
{
if (!f[i])
{
p[++tot]=i;
if (n/i>=m)
{
f[i]=m*i;
}
else
{
f[i]=f[1]+n/i*(i-1);
}
}
for (j=1;j<=tot&&i*p[j]<=n;++j)
{
v=i*p[j];
if (v<=n/2)
{
if (n/v>=m)
{
f[v]=m*v;
}
else
{
f[v]=f[i]+n/v*(v-i);
}
}
else
{
if (n/v>=m)
{
ans=max(ans,m*v);
}
else
{
ans=max(ans,f[i]+n/v*(v-i));
}
}
if (i%p[j]==0)
{
break;
}
}
}
cout<<ans;
return 0;
}
这个dp做法也是
loglog的做法:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=10000010;
int n,m,p[N/10],f[N/2],tot,ans;
int main()
{
int i,j,k,temp;
cin>>n>>m;
ans=max(m,n+m-1);
for (i=2;i<=n/2;++i)
{
if (!f[i])
{
p[++tot]=i;
f[i]=1;
}
for (j=1;j<=tot&&i*p[j]<=n;++j)
{
if (i*p[j]<=n/2)
{
f[i*p[j]]=i;
}
else
{
temp=0;
for (k=i*p[j];;k=(k<=n/2?f[k]:i))
{
if (n/k<m)
{
temp+=n/k*(k-(k<=n/2?f[k]:i));
}
else
{
temp+=m*k;
break;
}
}
ans=max(ans,temp);
}
if (i%p[j]==0)
{
break;
}
}
}
cout<<ans;
return 0;
}
P.S. 为什么要加上
设现在已经使用了的颜料编号构成的集合为
A ,若\ \exists\ i,\ j\notin A,\ i,\ j\in [1,\ n],\ gcd(A,\ i)>gcd(A,\ j) ,那么就不能选择颜料j 。
这个限制呢?因为这并不是最优策略,如:14 7
按照这个限制,答案是 12 6 3 9 1 2 4
实际上,若没有这个限制,答案是 12 4 8 2 6 10 14
P.P.S. 造数据的时候没想好..
P.P.P.S. 所以说到底为什么要加上上述限制呢?因为没有这个限制的时候出题人不会复杂度正确的做法..然后有一位julao@zhoutb2333 赛时看掉了这个限制想出了一个非常优秀的做法:
#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
using namespace std;
int n,m;
map<pii,ll> f;
ll F(int x,int y){
if(x==1)
return y;
if(f[{x,y}])
return f[{x,y}];
ll ret=0;
for(int i=2,pos;i<=x;i=pos+1)
pos=x/(x/i),ret=max(ret,F(x/i,min(y,x/i))*pos+y-min(y,x/i));
return f[{x,y}]=ret;
}
int main(){
cin>>n>>m;
cout<<F(n,m)<<endl;
return 0;
}