牛客练习赛59 E

题意

n n 堆石头,每次只能从一堆里面取任意个石头,代价是 k 2 k^2 k k 是选的石头数量。
每次会修改某堆石头的数量,对于每个修改都输出,在 m m 次内取完所有石头的最小代价

题解

单点更新。
首先 n 3 d p n^3dp 直接可以得出初始答案。

方法一:

由于是不考虑哪一堆的,所以我们也可以利用分治合并的方法。
把左右都合并好,然后再算上更新的。
具体可以类似于线段树,单点更新,向上合并。
每次更新的时候,将该部分的 d p dp 全部初始化(因为不是从前的了)。

#include<bits/stdc++.h>
#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 ,该方法为 q n l o g m qnlogm
具体表现为可撤销贪心。
首先每一堆初始都只用一次。
计算 c i , 1 = v a l i , 1 v a l i , 2 c_{i,1}=val_{i,1}-val_{i,2}
对于第 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}
即减去这个差值即可替换成第 i i 个用两次的情况。

为了答案最优,我们需要挑出 c i c_i 最大的,然后换掉,加上 v a l i , 2 v a l i , 3 val_{i,2}-val_{i,3}
这个时候用了 n + 1 n+1 次。
最终用到 m m 次为止,不断实现最优。

写代码直接记录最终是第几个用了几次最后再算即可。

#include<bits/stdc++.h>
#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;
    }
}
发布了203 篇原创文章 · 获赞 17 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/mxYlulu/article/details/104864230