[NOIP2015]推销员

题目大意

现有长度为N的两个数组a和s
有N个询问,第k个询问你需要找出一个i使得s[i]*2+a[i]+1..i-1中a数组前k-1大的值之和最大。

整体二分

注意到每个询问找出的那个最优的i是满足单调性的
因此可以对所有询问整体二分
用solve(l1,r1,l2,r2)表示当前处理第l1~r1个询问,他们最优的i范围在l2~r2。
令mid1=(l1+r1)/2,我们在l2~r2则找,看看那个是对于第mid1的最优i。找出最优的i设为mid2,那么分为solve(l1,mid1-1,l2,mid2),solve(mid1+1,r1,mid2,r2)。
我们易得知每一层的循环复杂度总和都是n,一共log n层,则复杂度为n log n。

主席树

如何得知第X个询问选择的i为y时的答案?
前y-1中最大的x-1个可以用主席树得到。

参考程序

#include<cstdio>
#include<cstring>
#include<cstdlib>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;  
typedef long long ll;
const ll maxn=100000+10;        
ll i,j,k,l,t,n,m,tot;
ll sum[maxn*25],num[maxn*25],left[maxn*25],right[maxn*25],root[maxn],ans[maxn],s[maxn],a[maxn];
void insert(ll l,ll r,ll y,ll p,ll &x){
    x=++tot;
    sum[x]=sum[y]+p;
    num[x]=num[y]+1;
    left[x]=left[y];right[x]=right[y];
    if (l==r) return;
    ll mid=(l+r)/2;
    if (p<=mid) insert(l,mid,left[y],p,left[x]);
    else insert(mid+1,r,right[y],p,right[x]);
}
ll get(ll l,ll r,ll x,ll y){
    if (y>num[x]) return -10000;
    if (!y) return 0;
    if (l==r) return sum[x]/num[x]*y;
    ll mid=(l+r)/2;
    if (num[right[x]]>=y) return get(mid+1,r,right[x],y);
    else return sum[right[x]]+get(l,mid,left[x],y-num[right[x]]);
}
void solve(ll l1,ll r1,ll l2,ll r2){
    if (l1>r1) return;
    ll mid1=(l1+r1)/2,mid2,t=0,k;
    ll i;
    fo(i,l2,r2){
        k=s[i]*2+a[i]+get(1,999,root[i-1],mid1-1);
        if (k>t){
            t=k;
            mid2=i;
        }
    }
    ans[mid1]=t;
    solve(l1,mid1-1,l2,mid2);solve(mid1+1,r1,mid2,r2);
}
int main(){
    freopen("salesman.in","r",stdin);freopen("salesman.out","w",stdout);
    scanf("%lld",&n);
    fo(i,1,n) scanf("%lld",&s[i]);
    fo(i,1,n){
        scanf("%lld",&a[i]);
        insert(1,999,root[i-1],a[i],root[i]);
    }
    solve(1,n,1,n);
    fo(i,1,n) printf("%lld\n",ans[i]);
    fclose(stdin);fclose(stdout);
    return 0;
}

你可能感兴趣的:([NOIP2015]推销员)