数模也是终于结束了。开始恢复vp。今天这场vp发挥比上次好一些,三题rank3600+。A,B题做的很顺利。C题标记没弄全多WA了两发。D题是个交互题,也是研究了一下。基本思路正确。
题目链接
A. Parkway Walk 贪心
题意:你依次要去n个地方。每个地方消耗 a i a_i ai的能量。你最开始有m能量,你可以随时停下来休息,可以恢复能量。只有能量大于等于当前地点所需能量才可以前进,询问最小需要恢复的能量。
思路:直接一开始就休息攒够不足的能量再出发即可。所以
a n s = m i n ( s u m − m , 0 ) ans=min(sum-m,0) ans=min(sum−m,0)
void Showball(){
int n,m;
cin>>n>>m;
vector<int> a(n);
int sum=0;
for(auto &x:a) cin>>x,sum+=x;
cout<<max(sum-m,0)<<endl;
}
B. Promo 前缀和+贪心
题意:有n件商品,每件商品的价格为 p i p_i pi,现在商家推出一个活动,买x件物品,这x件物品中的前y个便宜的商品就可以免费( x ≥ y x\geq y x≥y)。对于每个x和y,求出最多有多少金额可以免费。
思路:贪心,为了能够免费更多,那么我们只需要买最贵的x个物品,然后我们需要统计出这x个商品中前y个便宜的商品价格总和。
因为有q次询问。所以我们可以用前缀和来解决。就可以先对p从小到大排序,然后求前缀和,那么对于每次询问,我们要求的就是 [ n − x + 1 , n − x + y ] [n-x+1,n-x+y] [n−x+1,n−x+y]这段区间的和。记得开long long,否则会溢出。
void Showball(){
LL n,q;
cin>>n>>q;
vector<LL> a(n+1),s(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
sort(a.begin()+1,a.end());
for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
while(q--){
LL x,y;
cin>>x>>y;
cout<<s[n-x+y]-s[n-x]<<endl;
}
}
C. awoo’s Favorite Problem字符串
题意:给你两个长度为n且只含’a’,‘b’,'c’的字符串s和t。问你能否通过以下操作将s变为t。操作1:将"ab"变为“ba”。操作2:将"bc"变为“cb”。
思路:一道乱搞题,做法很多,这里说一下我赛时的想法。赛时也想了比较久,后面没找到什么结论,便开始一位一位讨论。
首先对于该位 i i i,如果 s [ i ] = t [ i ] s[i]=t[i] s[i]=t[i],就可以直接跳过。
其次如果 t [ i ] = t[i]= t[i]=‘a’,那么如果s想变成t只有该位为‘a’的情况才可以,否则不符合情况。
如果 t [ i ] = t[i]= t[i]=‘b’,那么除了s[i]该位也为‘b’的情况之外,还可以该位为’a’并且后面有连续的‘a’后接一个‘b’,例如aaab。那么就可以一直进行交换,把后面的‘b’换到这个地方。否则不符合情况。
如果 t [ i ] = t[i]= t[i]=‘c’,那么除了s[i]该位也为‘c’的情况之外,还可以该位为’b’并且后面有连续的‘b’后接一个‘c’,例如bbbc。那么就可以一直进行交换,把后面的‘c’换到这个地方。否则不符合情况。
接着我们把这个步骤进行模拟维护即可。具体实现看代码:
注意边界情况
void Showball(){
int n;
cin>>n;
string s,t;
cin>>s>>t;
if(s==t) {cout<<"YES"<<endl;return;}
s="?"+s+"?";
t="?"+t+"?";
bool flg=true;
for(int i=1;i<=n;i++){
if(s[i]==t[i]) continue;
if(t[i]=='a') {flg=false;break;}
else if(t[i]=='b') {
if(s[i]=='a') {
int j=i+1;
while(j<=n&&s[j]=='a') j++;
if(s[j]=='b') swap(s[i],s[j]);
else {flg=false;break;}
}
else {flg=false;break;}
}else{
if(s[i]=='b') {
int j=i+1;
while(j<=n&&s[j]=='b') j++;
if(s[j]=='c') swap(s[i],s[j]);
else {flg=false;break;}
}
else {flg=false;break;}
}
}
if(flg) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
D. Guess The String 交互+二分
题意:告诉你一个只含小写字母字符串的长度,你可以进行提问。
1.“?1 i ”会告诉你第i个字符是什么。
2.“? 2 l r ”会告诉你区间l到r之间有多少的不重复的字母。
你最多可以询问26次1,6000次询问2。
最后猜出这个字符串并且输出。
思路:交互题做的不多,赛时的想法是每次先询问1-i区间不同字母个数,如果增加就直接询问该位字母,否则就不断缩小区间找到那个与该位字母相同得到位置。但是没有想到二分优化,超过了询问限制。这题参考了t宝的解法,非常简洁!qrz。
首先,我们需要维护一个b数组, b [ i ] b[i] b[i]表示当前字符串的第i位字母最后一次出现的下标。我们对b数组进行排序。然后我们要去寻找该位字母在之前字符串最后出现的位置。就可以用二分查询。如果找到了,那么更新一下字符串,并且更新b数组的值。反之,没有找到,那么直接询问1即可,然后将该位置加入b数组。
void Showball(){
int n;
cin>>n;
string s="";
auto ask1=[&](int x){
cout<<"? 1 "<<x+1<<endl;
char res;
cin>>res;
return res;
};
auto ask2=[&](int l,int r){
cout<<"? 2 "<<l+1<<" "<<r+1<<endl;
int res;
cin>>res;
return res;
};
vector<int> b;
for(int i=0;i<n;i++){
sort(b.begin(),b.end());
int l=-1,r=(int)b.size()-1;
while(l<r){
int mid=(l+r+1)>>1;
if(ask2(b[mid],i)==(int)b.size()-mid) {
l=mid;
}else {
r=mid-1;
}
}
if(l==-1){
s+=ask1(i);
b.push_back(i);
}else{
s+=s[b[l]];
b[l]=i;
}
}
cout<<"! "<<s<<endl;
}