Educational Codeforces Round 66 (Rated for Div. 2)
A.直接模拟。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 typedef long long ll; 7 using namespace std; 8 9 ll n,k,T,ans; 10 11 int main(){ 12 for (cin>>T; T--; ){ 13 cin>>n>>k; ans=0; 14 for (ll x=n; x; x/=k,ans++) ans+=x%k; 15 cout<<ans-1<<endl; 16 } 17 return 0; 18 }
B.直接模拟,用栈记录下每层的循环次数,注意当总次数超过2^31时就直接记成2^31。
1 #include<cstdio> 2 #include<algorithm> 3 #include<iostream> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 typedef long long ll; 6 using namespace std; 7 8 const int N=200010; 9 int T,top; 10 ll x,t,s[N]; 11 12 int main(){ 13 s[++top]=1; 14 for (cin>>T; T--; ){ 15 char op[10]; cin>>op; 16 if (op[0]=='f') cin>>t,top++,s[top]=(s[top-1]<1ll<<32)?t*s[top-1]:s[top-1]; 17 else if (op[0]=='e') top--; else x+=s[top]; 18 if (x>=1ll<<32){ cout<<"OVERFLOW!!!"<<endl; return 0; } 19 } 20 cout<<x<<endl; 21 return 0; 22 }
C.离一个点最近的k个点一定是一个区间,枚举每个长度为k的区间即可。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 typedef long long ll; 7 using namespace std; 8 9 const int N=400010; 10 int n,k,T,ans,x,a[N]; 11 12 int main(){ 13 for (scanf("%d",&T); T--; ){ 14 scanf("%d%d",&n,&k); 15 rep(i,1,n) scanf("%d",&a[i]); 16 sort(a+1,a+n+1); ans=a[n]-a[1]; x=a[1]; 17 rep(i,1,n-k) if ((a[i+k]-a[i]+1)/2<ans) ans=(a[i+k]-a[i]+1)/2,x=(a[i]+a[i+k]+1)/2; 18 printf("%d\n",x); 19 } 20 return 0; 21 }
D.每设一个断点相当于将贡献加上之后所有数的和,那么将所有后缀和放在一起排序,取贡献最小的几个断点即可。注意开头是一定要设断点的。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 typedef long long ll; 7 using namespace std; 8 9 const int N=300010; 10 ll ans,sm[N]; 11 int n,k,s,a[N],id[N],p[N]; 12 bool cmp(int a,int b){ return sm[a]>sm[b]; } 13 14 int main(){ 15 scanf("%d%d",&n,&k); 16 rep(i,1,n) scanf("%d",&a[i]); 17 for (int i=n; i; i--) sm[i]=sm[i+1]+a[i],id[i]=i; 18 sort(id+2,id+n+1,cmp); 19 rep(i,1,k) p[id[i]]=1; 20 rep(i,1,n){ 21 if (p[i]) s++; 22 ans+=1ll*s*a[i]; 23 } 24 cout<<ans<<endl; 25 return 0; 26 }
E.倍增记录每个区间往后选2^i个区间最多能延伸到什么位置(保证这2^i个区间首尾不断),预处理的时候先按右端点排序,再用树状数组或者二分找到与当前区间首尾相接的右端点最靠右的区间。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 typedef long long ll; 7 using namespace std; 8 9 const int N=1000010; 10 int n,m,l,r,c[N],nxt[N][22]; 11 struct P{ int l,r; }p[N]; 12 bool operator <(const P &a,const P &b){ return a.r>b.r; } 13 14 int que(int x){ int res=0; for (x++; x; x-=x&-x) if (p[c[x]].r>p[res].r) res=c[x]; return res; } 15 void add(int x,int k){ for (x++; x<=500001; x+=x&-x) if (p[c[x]].r<=p[k].r) c[x]=k; } 16 17 int main(){ 18 scanf("%d%d",&n,&m); 19 rep(i,1,n) scanf("%d%d",&p[i].l,&p[i].r); 20 sort(p+1,p+n+1); 21 rep(i,1,n) nxt[i][0]=que(p[i].r),add(p[i].l,i); 22 rep(j,1,20) rep(i,1,n) nxt[i][j]=nxt[nxt[i][j-1]][j-1]; 23 rep(i,1,m){ 24 scanf("%d%d",&l,&r); int x=que(l),res=1; 25 if (x==n+1){ puts("-1"); continue; } 26 if (p[x].r>=r){ puts("1"); continue; } 27 for (int i=20; ~i; i--) if (nxt[x][i] && p[nxt[x][i]].r<r) res+=1<<i,x=nxt[x][i]; 28 res++; x=nxt[x][0]; 29 if (p[x].r<r) puts("-1"); else printf("%d\n",res); 30 } 31 return 0; 32 }
F.考虑将条件转化:[l,r]值域是[1,r-l+1]等价于[l,r]区间内数两两不同且max(l,r)=r-l+1。首先我们对于每个位置i,可以轻易算出最大的r[i]使得[i,r[i]]中的数两两不同。根据max想到最值分治,然后发现每次遍历区间长度一定不大于min(mid-l,r-mid),于是就在O(nlogn)时间内解决了。
最值分治大概就是,对于当前考虑区间[l,r]找到其中最大/小的数a[mid],然后对[l,mid-1]和[mid+1,r]分别递归下去。合并时,只遍历短的那一边,计算短的那一边的每个数与长的那一边整体产生的贡献。复杂度类比启发式合并可知显然为O(nlogn)。这题中就是每次找到最大值a[mid],然后计算有多少跨过mid的合法区间,根据之前的r[i]数组可以轻松得到答案。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=300010; 7 int n,ans,a[N],b[N],lst[N],lg[N],st[N][20]; 8 9 int Max(int x,int y){ return a[x]>a[y] ? x : y; } 10 11 int que(int l,int r){ int t=lg[r-l+1]; return Max(st[l][t],st[r-(1<<t)+1][t]); } 12 13 void work(int l,int r){ 14 int mid=que(l,r); 15 rep(i,max(l,mid-a[mid]+1),min(mid,r-a[mid]+1)) if (b[i]>=a[mid]) ans++; 16 if (l<mid) work(l,mid-1); 17 if (r>mid) work(mid+1,r); 18 } 19 20 int main(){ 21 scanf("%d",&n); 22 rep(i,1,n) scanf("%d",&a[i]),st[i][0]=i; 23 b[n+1]=n+1; rep(i,1,n) lst[i]=n+1; 24 for (int i=n; i; i--) b[i]=min(b[i+1]+1,lst[a[i]]-i),lst[a[i]]=i; 25 rep(i,2,n) lg[i]=lg[i>>1]+1; 26 rep(j,1,lg[n]) rep(i,1,n-(1<<j)+1) st[i][j]=Max(st[i][j-1],st[i+(1<<(j-1))][j-1]); 27 work(1,n); printf("%d\n",ans); 28 return 0; 29 }
G.一个显然的DP是,f[i][j]表示前i个数分j段的最小总和,转移枚举第j段的开头,复杂度O(kn^2)。考虑对每个j做类似CDQ分治,先递归算出[l,mid]和[mid+1,r]两个区间的答案,再计算[mid+1,r]中的每个位置的最后一段开头在[l,mid]中时,能否更新答案。我们从mid开始向前向后分别求后缀和与前缀和,然后分“最后一段的最大值在[l,mid]还是[mid+1,r]”两种情况转移,发现方程是一个关于前缀后缀和的一个一次函数。于是问题变为,分治后在线往直线集中加入一条新直线,并实时查询集合中所有直线某个横坐标上的值的最大值。用单调栈维护一个上凸壳,查询时二分即可。注意一个细节是,当新加入的直线斜率和上一条直线斜率相同时,普通的计算交点方法就错了。于是复杂度为O(nklogn)。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=20010,inf=1e9; 7 int n,k,top,a[N],f[N],g[N],pre[N],suf[N]; 8 struct L{ int k,b; int F(int x){ return k*x+b; } }q[N]; 9 10 bool cmp(L a,L b,L c){ return 1ll*(a.k-b.k)*(c.b-a.b)<=1ll*(a.k-c.k)*(b.b-a.b); } 11 12 void ins(L x){ 13 while (top && x.k>=q[top].k) x.b=min(x.b,q[top].b),top--; 14 while (top>1 && cmp(q[top-1],q[top],x)) top--; 15 q[++top]=x; 16 } 17 18 int calc(int x){ 19 if (!top) return inf; 20 int l=1,r=top; 21 while (l<r){ 22 int mid=(l+r)>>1; 23 if (q[mid].F(x)<=q[mid+1].F(x)) r=mid; else l=mid+1; 24 } 25 return q[l].F(x); 26 } 27 28 void solve(int l,int r){ 29 if (l==r) return; 30 int mid=(l+r)>>1; 31 solve(l,mid); solve(mid+1,r); 32 suf[mid+1]=0; for (int i=mid; i>=l; i--) suf[i]=max(suf[i+1],a[i]); 33 pre[mid]=0; rep(i,mid+1,r) pre[i]=max(pre[i-1],a[i]); 34 top=0; 35 for (int i=r,j=l; i>mid; i--){ 36 while (j<=mid && suf[j+1]>=pre[i]) if (g[j++]<inf) ins((L){suf[j],g[j-1]-(j-1)*suf[j]}); 37 f[i]=min(f[i],calc(i)); 38 } 39 top=0; 40 for (int i=mid+1,j=mid; i<=r; i++){ 41 while (j>=l && suf[j+1]<=pre[i]) if (g[j--]<inf) ins((L){j+1,g[j+1]}); 42 f[i]=min(f[i],calc(-pre[i])+i*pre[i]); 43 } 44 } 45 46 int main(){ 47 scanf("%d%d",&n,&k); 48 rep(i,1,n) scanf("%d",&a[i]),pre[i]=max(pre[i-1],a[i]); 49 rep(i,1,n) f[i]=pre[i]*i; 50 rep(i,2,k){ 51 rep(j,1,n) g[j]=f[j],f[j]=inf; 52 solve(1,n); 53 } 54 printf("%d\n",f[n]); 55 return 0; 56 }