NWERC 2020A Atomic Energy(背包+思维)

对于这题,观察到数据范围,首先观察出第一个性质

最后只需要找到两个和大于n的,减去这两个值后,k就可以通过任意选择组合而来。

如果k的数据范围很小,那么我们可以直接背包求取答案。

现在k有1e9,那就需要其他的性质。

又因为我们观察到数是连续的,因此假如当时我们有一个效率最高的数,也就是a[i]/i最小的那个。

那么最后的正解里面,一定有大部分数和可以成为这个i的倍数,这样就可以用这个i来替换掉,从而缩减范围。

由于数是连续的,根据鸽巢原理,最后剩下不能组成i的倍数的数不超过m+1个。我们预处理前面的背包。

所以我们只需要找到一个比这个上界大的数,那么k就是由j*am+f[k-j*am]更新而来。

这一步是因为由于在正解中和不为选中i的数最多m+1个,因此用他们组成的结果不会大于(m+1)*n,所以大于他的数都需要am来更新。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=1e6+10;
const int mod=998244353;
ll f[N];
ll a[N];
ll ff[N];
int m;
int main(){
    ios::sync_with_stdio(false);
    int i,j;
    int n,q;
    cin>>n>>q;
    for(i=1;i<=n;i++){
        cin>>a[i];
        if(!m||1.0*a[i]/i<1.0*a[m]/m){
            m=i;
        }
    }
    for(i=0;i<N;i++){
        f[i]=1e12;
        ff[i]=1e12;
    }
    f[0]=0;
    for(i=1;i<=n;i++){
        for(j=i;j<=2e4;j++){
            f[j]=min(f[j],f[j-i]+a[i]);
        }
    }
    for(i=1;i<=n;i++){
        for(j=n-i+1;j<=n;j++){
            ff[i+j]=min(ff[i+j],a[i]+a[j]);
        }
    }
    while(q--){
        ll k;
        cin>>k;
        ll ans=1e18;
        if(k<=n){
            cout<<a[k]<<endl;
            continue;
        }
        for(i=n+1;i<=2*n;i++){
            if(i>k)
                continue;
            ll g=k-i;
            ll tmp=ff[i];
            if(g<=2e4){
                ans=min(ans,tmp+f[g]);
                continue;
            }
            ll d=g-2e4;
            d=d/m+1;
            int sign=0;
            tmp+=(d*a[m])+f[g-d*m];
            ans=min(ans,tmp);
        }
        cout<<ans<<endl;
    }
    return 0;
}
View Code
没有人不辛苦,只有人不喊疼
原文地址:https://www.cnblogs.com/ctyakwf/p/14725373.html