2020牛客暑期多校(八)

K Kabaleo Lite(前缀和+高精度(大数处理))

(1)把一个超过long long范围的数字拆成两个long long存储!

(2)计算前缀和的操作非常聪明

代码来自:

https://blog.nowcoder.net/n/3b2e46a2f0ff4201b1587b1cffb08d6d

#include<bits/stdc++.h>
using namespace std;
const long long N=1e5+9,mod=1e14;                  //控制 m 在1e14范围之内 
long long a[N],b[N],s[N],n,m,ans;
int main()
{
    int T,l,r,i,j,k;
    scanf("%d",&T);
    for(k=1;k<=T;k++)
    {
        memset(a,0,sizeof(a)),memset(b,0,sizeof(b)),memset(s,0,sizeof(s));
        scanf("%lld",&n);
        for(i=0;i<n;i++) scanf("%lld",&a[i]);
        scanf("%lld",&b[0]);
        for(i=1;i<n;i++) scanf("%lld",&b[i]),b[i]=min(b[i],b[i-1]);        //b:个数 
        s[0]=a[0];
        for(i=1;i<n;i++) s[i]=s[i-1]+a[i];                               //s:前缀和  
        l=r=ans=0,m=s[l]*b[l];                                    //以上均从0开始计数 
        while(r<n)                      //遍历
        {
            while(r<n && s[r]<=s[l]) r++;
            if(r==n)break;                            //选取第一个前缀和大于当前的位置 
            m+=b[r]*(s[r]-s[l]),ans+=m/mod,m%=mod;          //m += b[r] * (s[r] - s[l])  加上这一段的数目
            l=r;                                    //更新 l
        }
        printf("Case #%d: %lld ",k,b[0]);
        if(ans) printf("%lld%014lld\n",ans,m);
        else printf("%lld\n",m);
    }
}

略为暴力的作法:

(1)用priority队列从大到小储存前缀和

(2)从前缀和大到小遍历,减去当前的全部个数,下一个前缀和如果没有这么多个数就等于被减完了,跳过,有的话就算上多出来的部分。cnt一直在增加,相当于减掉的越来越多

(3)用__int128储存大数

代码来自:

https://blog.nowcoder.net/n/6cecfd81e1e3465aa75b2b82e29973e9

#include<bits/stdc++.h>
#define ll __int128
#define pii pair<ll,pair<ll,ll> >
using namespace std;
 
inline __int128 read(){
    __int128 x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
 
inline void print(__int128 x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>9)
        print(x/10);
    putchar(x%10+'0');
}
 
const ll maxn=1e5+10;
ll a[maxn],b[maxn],c[maxn];
priority_queue<pii> q;
 
int main()
{
 
    ll t;
    t=read();
    ll kk=1;
    while(t--){
        ll n;
        n=read();
        for(ll i=0;i<n;i++) a[i]=read();
        for(ll i=0;i<n;i++) b[i]=read();
        for(ll i=0;i<n;i++){
            if(i) c[i]=c[i-1]+a[i];
            else c[i]=a[i];
        }
        __int128 ans=0;
        ll bb=b[0];
        for(ll i=0;i<n;i++){
            bb=min(bb,b[i]);
            q.push({c[i],{bb,i}});
        }
        ll cnt=0,pos=n;
        while(!q.empty()){
            ll x=q.top().first,y=q.top().second.first,z=q.top().second.second;
            q.pop();
            if(y<=cnt||pos<=z) continue;
            ans+=x*(y-cnt);
            cnt=y;
            pos=z;
        }
        cout<<"Case #";
        print(kk++);
        cout<<": ";
        print(b[0]);
        cout<<" ";
        print(ans);
        cout<<endl;
    }
    return 0;
}

最为暴力的作法:

(1)也是把前缀和数组从大到小排序

(2)用线段树全部区间修改个数

这就是我的代码orz

#include<bits/stdc++.h>
using namespace std;
#define ll __int128
//区间修改
//点查询
//线段树记录每个序号的数目
const int inf=1e9+1e5;
struct __Sum{
    ll sum;
    int id;
} Sum[100004];
ll num[100004<<2],add[100004<<2];
ll mi=inf;
int first,vis;
void push_up(int rt){
    num[rt]=num[rt<<1]+num[rt<<1|1];
}
void push_down(int rt,int m){
    if(add[rt]){
        add[rt<<1]+=add[rt];
        add[rt<<1|1]+=add[rt];
        num[rt<<1]+=(m-(m>>1))*add[rt];
        num[rt<<1|1]+=(m>>1)*add[rt];
        add[rt]=0;
    }
}
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
void build(int l,int r,int rt){
    add[rt]=0;
    if(l==r){
        scanf("%lld",&num[rt]);
        if(vis==0){first=num[rt];vis=1;}
        mi=min(mi,num[rt]);
        num[rt]=mi;
        return;
    }
    int mid=(l+r)>>1;
    build(lson);
    build(rson);
    push_up(rt);
}
void update(int a,int b,int c,int l,int r,int rt){
    if(a<=l&&b>=r){
        num[rt]+=(r-l+1)*c;
        add[rt]+=c;
        return;
    }
    push_down(rt,r-l+1);
    int mid=(l+r)>>1;
    if(a<=mid) update(a,b,c,lson);
    if(b>mid) update(a,b,c,rson);
    push_up(rt);
}
int query(int a,int b,int l,int r,int rt){
    if(a<=l&&b>=r) return num[rt];
    push_down(rt,r-l+1);
    int mid=(l+r)>>1;
    int ans=0;
    if(a<=mid) ans+=query(a,b,lson);
    if(b>mid) ans+=query(a,b,rson);
    return ans;
}
bool cmp(__Sum a,__Sum b){
    return a.sum>b.sum;
}
void init(){
    memset(num,0,sizeof(num));
    memset(add,0,sizeof(add));
    memset(Sum,0,sizeof(Sum));
    mi=inf;
    Sum[0].sum=0;
    vis=0;
}
inline void print(__int128 x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>9)
        print(x/10);
    putchar(x%10+'0');
}
int main(){
    int t;
    scanf("%d",&t);
    for(int c=1;c<=t;++c){
        int n;
        scanf("%d",&n);
        init();
        int tmp;
        for(int i=1;i<=n;++i){
            scanf("%d",&tmp);
            Sum[i].sum=tmp+Sum[i-1].sum;
            Sum[i].id=i;
        }
        build(1,n,1);
        sort(Sum+1,Sum+1+n,cmp);
        int cnt=0;
        __int128 ans=0;
        //num[1]盘,那么计算num[1]次前缀和即可
        for(int i=1;i<=n;++i){
            int tmp=query(Sum[i].id,Sum[i].id,1,n,1);
            if(tmp<=0) continue;
            //当前的数目
            //前面扣除为0之后,后面也要减掉这个值。
            if(cnt+tmp>=first){
                tmp=first-cnt;
                ans+=(ll)tmp*Sum[i].sum;
                break;
            }
            cnt+=tmp;
            ans+=(ll)tmp*Sum[i].sum;
            update(1,n,-tmp,1,n,1);
            //1到n都剪掉当前的数目
        }
        printf("Case #%d: %d ",c,first);
        print(ans);
        printf("\n");
    }
}
posted @ 2020-08-04 10:45  IcecreamArtist  阅读(92)  评论(0编辑  收藏  举报