【codeforces】【比赛题解】#940 CF Round #466 (Div. 2)
人生的大起大落莫过如此,下一场我一定要回紫。
【A】Points on the line
题意:
一个直线上有\(n\)个点,要求去掉最少的点,使得最远两点距离不超过\(d\)。
题解:
暴力两重for,更新答案。
#include<cstdio> #include<algorithm> using namespace std; #define F(i,a,b) for(int i=(a);i<=(b);++i) int n,q,ans=0; int a[100001]; int main(){ scanf("%d%d",&n,&q); F(i,1,n) scanf("%d",a+i); sort(a+1,a+n+1); F(i,1,n){ int xx=a[i], kk=-1; F(j,i,n){ if(a[j]-xx>q) break; kk=j; } ans=max(ans,kk-i+1); } printf("%d",n-ans); return 0; }
【B】Our Tanya is Crying Out Loud
题意:
一个数\(n\),每次你可以花费\(A\)的代价把它除以\(k\)(如果\(k|n\)),或者花费\(B\)的代价把它减一。问把这个数变成1的最少代价。
题解:
暴力上,不能除减到能除为止,能除看是除了花费少还是不除花费少。
注意特判\(k=1\)。
#include<cstdio> #define ll long long int n,k,A,B; ll ans; int main(){ scanf("%d%d%d%d",&n,&k,&A,&B); if(k==1) {printf("%lld",(ll)(n-1)*A);return 0;} while(n!=1){ int x=n/k*k; if(x==0) {ans+=(ll)(n-1)*A; break;} if(n!=x) ans+=(ll)(n-x)*A, n=x; else{ x=n/k; if((ll)(n-x)*A<=B) ans+=(ll)(n-x)*A; else ans+=B; n=x; } } printf("%lld",ans); return 0; }
【C】Phone Numbers
题意:
限定字符集,求长度为\(k\)的,字典序比给定长度为\(n\)的字符串大的最小的字符串。
题解:
①\(k>n\)时,在原串后补上最小的字符。
②\(k<=n\)时,答案为原串的前\(k\)位+1,即比原串\(k\)位前缀大的第一个字符串。
#include<cstdio> #define F(i,a,b) for(int i=(a);i<=(b);++i) #define F2(i,a,b) for(int i=(a);i<(b);++i) int n,k,tot; int px[150]; bool used[150]; char str[100010],use[30]; char ans[100010]; int main(){ scanf("%d%d",&n,&k); scanf("%s",str); F2(i,0,n) used[str[i]]=1; F(i,1,140) if(used[i]) use[++tot]=i; F(i,1,tot) px[use[i]]=i; if(k>n){ printf("%s",str); F(i,n+1,k) printf("%c",use[1]); return 0; } F2(i,0,k) ans[i]=str[i]; ans[k]='\0'; int x=k-1; while(px[ans[x]]==tot){ ans[x]=use[1]; --x; } ans[x]=use[px[ans[x]]+1]; printf("%s",ans); return 0; }
【D】Alena And The Heater
题意:
已知长度为\(n\)的数组\(a\)和等长的01串\(b\),和通过\(a\)生成\(b\)的方法:
\(b_1=b_2=b_3=b_4=0\)
对于\(5\le i\le n\)
如果\(a_i,a_{i-1},a_{i-2},a_{i-3},a_{i-4}>r\) 并且\(b_{i-1}=b_{i-2}=b_{i-3}=b_{i-4}=1\),那么\(b_i=0\)
如果\(a_i,a_{i-1},a_{i-2},a_{i-3},a_{i-4}<l\) 并且\(b_{i-1}=b_{i-2}=b_{i-3}=b_{i-4}=0\),那么\(b_i=1\)
如果以上两者均不满足,那么\(b_i=b_{i-1}\)。
试求出\(l,r\)的值。
题解:
可以看到,对计算有贡献的只有\(b\)中出现连续的4个相同数字的情况。
通过这些情况,我们可以确定\(l,r\)的范围,进而求出\(l,r\)的可能值。
因为题目保证有解,只需输出最极端的值(\(l_{min},r_{max}\))即可。
#include<cstdio> #include<algorithm> using namespace std; #define F(i,a,b) for(int i=(a);i<=(b);++i) int n,ansll=-1000000000,ansrr=1000000000; int a[100001]; char b[100009]; int main(){ scanf("%d",&n); F(i,1,n) scanf("%d",a+i); scanf("%s",b+1); F(i,5,n){ if(b[i-4]==b[i-3]&&b[i-3]==b[i-2]&&b[i-2]==b[i-1]){ if(b[i-1]=='0'){ int x=max(max(max(max(a[i],a[i-1]),a[i-2]),a[i-3]),a[i-4]); if(b[i]=='1'){ //l > max(a 1~5) ansll=max(ansll,x+1); } } else{ int x=min(min(min(min(a[i],a[i-1]),a[i-2]),a[i-3]),a[i-4]); if(b[i]=='0'){ //r < min(a 1~5) ansrr=min(ansrr,x-1); } } } } printf("%d %d",ansll,ansrr); return 0; }
【E】Cashback
题意:
给定一个长度为\(n\)的数组\(a\)和一个常数\(c\)。你可以把\(a\)数组分割成连续的若干段,每一段的代价分别计算:
对于长度为\(len\)的一段,其代价为其元素总和,减去最小的\(\left \lfloor \frac{len}{c} \right \rfloor\)个元素。
求最小总代价。
题解:
既然要让代价最小,我们转化为求最大的价值,再用所有的元素和减去这个价值即可。
那就变成了长度为\(len\)的一段,其价值为最小的\(\left \lfloor \frac{len}{c} \right \rfloor\)个元素的和。
首先我们注意到一个结论:有贡献价值的分段,其长度正好为\(c\)时,没有其他方法比它更优。
证明请自行脑补……因为我也没想清楚,当时考场上瞎猜的结论,有兴趣的读者不妨看看http://www.cnblogs.com/kkkkahlua/p/8468896.html。
那么,我们通过单调队列处理出每个长为\(c\)的区间的最小值,记为\(b\)数组。
那么有DP方程\(f[i]=(max_{j=1}^{i-c}f[i])+b[i-c+1]\),表示前\(i\)个数,最后一个区间必须选取的最大价值,答案为\(Sum-max(f[i])\)。
#include<cstdio> #include<algorithm> using namespace std; #define F(i,a,b) for(int i=(a);i<=(b);++i) #define ll long long int n,c; int a[100009],b[100009]; int que[100009],l=1,r=0; ll sum,f[100009],g[100009]; int main(){ scanf("%d%d",&n,&c); F(i,1,n) scanf("%d",a+i); F(i,1,n) sum+=a[i]; if(c>n) {printf("%lld",sum); return 0;} F(i,1,c){ while(l<=r&&a[que[r]]>=a[i]) --r; que[++r]=i; } b[1]=a[que[l]]; F(i,c+1,n){ if(que[l]<=i-c) ++l; while(l<=r&&a[que[r]]>=a[i]) --r; que[++r]=i; b[i-c+1]=a[que[l]]; } F(i,1,n-c+1){ if(i<=c) f[i]=b[i]; else{ f[i]=b[i]+g[i-c]; } g[i]=max(g[i-1],f[i]); } printf("%lld",sum-g[n-c+1]); return 0; }