小可的疑惑(problem)
赛前:
n=k*b+c,使得b*c取max
在和相同的情况下,两数越接近乘积就越大。但是担心正好被整除导致数字不是最大的,直接写了暴力
根据数据范围,暴力可以得70分
赛后:
思路:
n=k*b+c,要使b*c最大,k一定为1,此时n=b+c,所以要想让b*c最大,只需要让b和c在为整数的情况下,尽可能的接近就可以了。
若n为复数,b一定不能等与n/2,否则答案为0.
ac代码:
#include<iostream>
using namespace std;
int n,k;
int main(){
scanf("%d",&n);
long long k=n/2+1;
printf("%ld",(n%k)*k);
return 0;
}
注意:
最后的乘积可能超过int范围,使用long long。
达达的考验(test)
赛前:
给定一个字符串,求里面的每一个数字+5后的和
字符串在1e5之内可以直接便利,遇到数字就加起来,最后直接输出
100
没看见前面的-1是第几个考验->0
赛后:
思路:
便利整个字符串,遇到数字的时候就记录一下并加起来,最后输出就可以了。
ac代码:
#include<iostream>
#include<algorithm>
using namespace std;
int n;
string s;
long long sum=0;
int main(){
scanf("%d",&n);
cin>>s;
for(int i=0;i<s.size();i++){
long long sum1=0;
while(s[i]>='0'&&s[i]<='9'){
sum1=sum1*10+(s[i++]-'0');
}
sum+=sum1+5;
}
printf("%ld",sum);
return 0;
}
注意:
使用long long
小可赛马(horse)
赛前:
给定一个长度为n的计划表,其中i个数字需要出现a[i]次才可以出战。接下来输入一个m,查询m次在l到r的序列中有没有k个数可以出战。
l到r的区间数使用前缀和,但数据范围1e5,数组需要开1e10显然不行,放弃。时间原因直接使用暴力,有40%的数据在暴力范围之内。
40
赛后:
思路:
解决区间求和问题,我们使用前缀和。n的范围是1e5,所以二维前缀和第一层开10^5肯定是没有问题,但是第二层如果开10^5的话空间肯定会超限。在一个10^5的数组中最多有478个数是可以出战的,像这样:1,2,2,3,3,3,4,4,4,4……478.所以二维前缀和数组只需要开到cnt[1e5][500]就可以了。
然后再使用离散化,是每个数组对应一个值,例如:1 5 700 1000 ->对应下标 1 2 3 4.问题就解决了。
如下使用:
for(int i=1;i<=n;i++){
scanf("%d",&x[i]);
if(x[i]<=n) a[x[i]]++;
}
for(int i=1;i<=n;i++){
if(a[i]>=i) b[++sum]=i;
}
for(int i=1;i<=sum;i++){
for(int j=1;j<=n;j++){
cnt[j][i]=cnt[j-1][i]+(x[j]==b[i]);
}
}
时间复杂度最高为5*10^7,不会超限。
ac代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+5;
int n;
int a[N],cnt[N][500],x[N],b[N],sum;
int m,l,r,k;
int f(int cnt,int k){
if(k==1) return cnt;
if(k==2) return cnt*(cnt-1)/2;
if(k==3) return cnt*(cnt-1)*(cnt-2)/6;
if(k==4) return cnt*(cnt-1)*(cnt-2)*(cnt-3)/24;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&x[i]);
if(x[i]<=n) a[x[i]]++;
}
for(int i=1;i<=n;i++){
if(a[i]>=i) b[++sum]=i;
}
for(int i=1;i<=sum;i++){
for(int j=1;j<=n;j++){
cnt[j][i]=cnt[j-1][i]+(x[j]==b[i]);
}
}
scanf("%d",&m);
while(m--){
scanf("%d%d%d",&l,&r,&k);
int cnt1=0;
for(int i=1;i<=sum;i++){
if(cnt[r][i]-cnt[l-1][i]>=b[i]){
cnt1++;
}
}
cout<<f(cnt1,k)<<"\n";
}
return 0;
}
注:
k的范围较小,所以可以直接枚举来做,这里使用的是C(m,n)=m!/(n!*(m-n)!);
括号专家(expert)
赛前:
给定一个n、a、b,一个长度为n的只包含“(”和“)”的字符串,每组匹配的括号有0,1,2三种状态,0状态的收益为0,1状态的收益为a,2状态的收益为b,相邻的不匹配的括号状态不能相同,求最大收益。
40%的数据()()()()⋯这样的,这种序列只有一种情况:一组a状态,一组为b状态,判断单复即可。
40
赛后:
思路:
若[1,x]是一对匹配括号,那么它可以将字符串分为两个部分:[1,x]和[x+1,n];
数组dp[l,r,p,q]表示点l为p状态,r为q状态。则有:
[1,x]均为p状态
那么[x+1]为!p状态需要枚举
[n]则为p状态
用动态规划如下使用:
int ans=0;
for(int i=0;i<=2;i++){
if(i!=p){
ans=max(ans,dfs(x[l]+1,r,i,q));
}
}
dp[l][r][p][q]=ans+dfs(l,x[l],p,p);
return dp[l][r][p][q];
考虑一种特殊情况:当[1,x]是[1,n]的时候,那么它就没法把整个字符串分为两部分了
此时,我们把字符串变成[2,n-1]继续使用以上方法
代码如下:
if(p!=q) return 0;
int ans=0;
for(int i=0;i<=2;i++){
for(int j=0;j<=2;j++){
if(i!=p&&j!=q) ans=max(ans,dfs(l+1,r-1,i,j));
}
}
dp[l][r][p][q]=ans+c[p];
return dp[l][r][p][q];
注意:要记得加上[1,x]的价值
求[1,x]可以使用栈进行枚举:
for(int i=0;i<s.size();i++){
if(s[i]=='(') st.push(i);
else{
x[i+1]=st.top()+1;
x[st.top()+1]=i+1;
st.pop();
}
}
ac代码:
#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
const int N=1e3+5;
int n,x[N];
int dp[N][N][3][3],c[3];
string s;
stack<int> st;
int dfs(int l,int r,int p,int q){
if(l>=r) return 0;
if(dp[l][r][p][q]!=0) return dp[l][r][p][q];
if(x[l]==r){
if(p!=q) return 0;
int ans=0;
for(int i=0;i<=2;i++){
for(int j=0;j<=2;j++){
if(i!=p&&j!=q) ans=max(ans,dfs(l+1,r-1,i,j));
}
}
dp[l][r][p][q]=ans+c[p];
return dp[l][r][p][q];
}else{
int ans=0;
for(int i=0;i<=2;i++){
if(i!=p){
ans=max(ans,dfs(x[l]+1,r,i,q));
}
}
dp[l][r][p][q]=ans+dfs(l,x[l],p,p);
return dp[l][r][p][q];
}
}
int main(){
scanf("%d%d%d",&n,&c[1],&c[2]);
cin>>s;
for(int i=0;i<s.size();i++){
if(s[i]=='(') st.push(i);
else{
x[i+1]=st.top()+1;
x[st.top()+1]=i+1;
st.pop();
}
}
int ans=0;
for(int i=0;i<=2;i++){
for(int j=0;j<=2;j++){
ans=max(ans,dfs(1,n,i,j));
}
}
printf("%d",ans);
return 0;
}
做题顺序:
2->3->1->4
时间分配:前10分钟把每个题都看了一遍,第二道和第三道有一点思路,快速写完第二道,卡在第三道,共做了20分钟,发现时间不多了,写了第一道和第三道的暴力,第四题写了其中一种情况的代码。总用时40分钟。