n n n堆石头,每次只能从一堆里面取任意个石头,代价是 k 2 k^2 k2, k k k是选的石头数量。
每次会修改某堆石头的数量,对于每个修改都输出,在 m m m次内取完所有石头的最小代价
单点更新。
首先 n 3 d p n^3dp n3dp直接可以得出初始答案。
方法一:
由于是不考虑哪一堆的,所以我们也可以利用分治合并的方法。
把左右都合并好,然后再算上更新的。
具体可以类似于线段树,单点更新,向上合并。
每次更新的时候,将该部分的 d p dp dp全部初始化(因为不是从前的了)。
#include
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn = 405;
int n,m;
ll dp[maxn<<2][maxn],len[maxn<<2];
int A[maxn],s;
ll cal(int x,int y){
if(x%y==0)return 1ll*(x/y)*(x/y)*y;
else{
ll r1=x/y,r2=x%y;
return r2*(r1+1)*(r1+1)+(y-r2)*r1*r1;
}
}
void update(int k){
int ls=k<<1,rs=k<<1|1;
memset(dp[k],0x3f,sizeof(dp[k]));
for(int i=len[ls];i<=m;i++){
for(int j=len[rs];i+j<=m;j++){
dp[k][i+j]=min(dp[k][i+j],dp[ls][i]+dp[rs][j]);
}
}
}
void build(int l,int r,int k){
int ls=k<<1,rs=k<<1|1,mid=(l+r)>>1;
len[k]=r-l+1;
if(l==r){
memset(dp[k],0x3f,sizeof(dp[k]));
for(int i=1;i<=m;i++)dp[k][i]=cal(A[r],i);
return ;
}
build(l,mid,ls);
build(mid+1,r,rs);
update(k);
}
void slove(int l,int r,int k,int x){
int ls=k<<1,rs=k<<1|1,mid=(l+r)>>1;
if(l==r){
memset(dp[k],0x3f,sizeof(dp[k]));
for(int i=1;i<=m;i++)dp[k][i]=cal(A[r],i);
return ;
}
if(x<=mid)slove(l,mid,ls,x);
else slove(mid+1,r,rs,x);
update(k);
}
int main(){
cin>>n>>m;
ll sum=0;
FOR(i,1,n)cin>>A[i],sum+=A[i];
build(1,n,1);
int T;cin>>T;
while(T--){
int x,v;cin>>x>>v;
A[x]=v;
slove(1,n,1,x);
cout<<dp[1][m]<<endl;
}
}
方法二:
复杂度更优,上面是 q n 2 l o g n qn^2logn qn2logn,该方法为 q n l o g m qnlogm qnlogm
具体表现为可撤销贪心。
首先每一堆初始都只用一次。
计算 c i , 1 = v a l i , 1 − v a l i , 2 c_{i,1}=val_{i,1}-val_{i,2} ci,1=vali,1−vali,2
对于第 i i i个, v a l i , 2 = v a l i , 1 − ( v a l i , 1 − v a l i , 2 ) = v a l i , 1 − c i , 1 val_{i,2}=val_{i,1}-(val_{i,1}-val_{i,2})=val_{i,1}-c_{i,1} vali,2=vali,1−(vali,1−vali,2)=vali,1−ci,1
即减去这个差值即可替换成第 i i i个用两次的情况。
为了答案最优,我们需要挑出 c i c_i ci最大的,然后换掉,加上 v a l i , 2 − v a l i , 3 val_{i,2}-val_{i,3} vali,2−vali,3
这个时候用了 n + 1 n+1 n+1次。
最终用到 m m m次为止,不断实现最优。
写代码直接记录最终是第几个用了几次最后再算即可。
#include
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn = 405;
int n,m;
int A[maxn];
ll cal(int x,int y){
if(x%y==0)return 1ll*(x/y)*(x/y)*y;
else{
ll r1=x/y,r2=x%y;
return r2*(r1+1)*(r1+1)+(y-r2)*r1*r1;
}
}
ll slove(int x,int y){
return cal(x,y)-cal(x,y+1);
}
int main(){
cin>>n>>m;
FOR(i,1,n)cin>>A[i];
int T;cin>>T;
while(T--){
int x,v;scanf("%d%d",&x,&v);
A[x]=v;
priority_queue<pair<ll,pair<int,int> > >q;
for(int i=1;i<=n;i++){
q.push(make_pair(slove(A[i],1),make_pair(A[i],1)));
}
int res=m-n;
while(res){
res--;
auto it=q.top();q.pop();
int val=it.second.first,id=it.second.second+1;
q.push(make_pair(slove(val,id),make_pair(val,id)));
}
ll ans=0;
while(!q.empty()){
auto it=q.top();q.pop();
int val=it.second.first,id=it.second.second;
ans+=cal(val,id);
}
cout<<ans<<endl;
}
}