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"); } }