2024年度第五届全国大学生算法设计与编程挑战赛(春季赛)(正式赛A,E,G,H,J)(测试赛A~B)
正式赛
A. 送个分吧


暴力求所有子串复杂度接近5e3*5e3*5e3,substr的复杂度是线性的,考虑拼接,如果枚举所有的字符串,取5个最小的放到SET里面比较,大的删了复杂度可能也刚好爆了。
因为K很小,所以太长的字符串永远不需要,只需要暴力枚举长度为1~5的子串就行了,然后取第k个。
也可以直接从最小的地方开始找5个。
#include <bits/stdc++.h>
//#define int long long
#define per(i,j,k) for(int (i)=(j);(i)<=(k);++(i))
#define rep(i,j,k) for(int (i)=(j);(i)>=(k);--(i))
#define debug(a) cout<<#a<<"="<<a<<endl
#define all(x) x.begin(),x.end()
#define EX exit(0)
#define fr first
#define se second
#define endl '\n'
using namespace std;
using ll=long long;
void solve(){
string s;
int k;
cin>>s>>k;
map<string,int>mp;
per(i,0,s.length()-1){
mp[s.substr(i)] = 1;
}
set<string>st;
for(auto [str,v]:mp){
for(int i = 1;i<=str.size();++i){
string t = str.substr(0,i);
if(st.count(t))continue;
k--;
st.insert(t);
if(k==0){
cout<<t;
return;
}
}
}
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr);
int t=1;
while(t--)solve();
return 0;
}
E. 语言学习中


2H<->1X
2X<->1H
add 3H,3X
对于任意位置的X,我们只能把它变成:HH(或者使用免费的add)
所以对于询问中的两个串,能形成本质区别的,只有前面两个等价式,第三个是免费操作,所以没区别。
那么我们把串全部变成H,显然如果变成H之后两个串的差值是3的倍数(可以使用免费操作),那就有解。
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
string h,x;
int preh[100001],prex[100001],q;
int main(){
cin>>h>>x;
for (int i=1;i<=h.size();++i){
preh[i]=preh[i-1];
if(h[i-1]=='H')preh[i]++;
}
for (int i=1;i<=x.size();++i){
prex[i]=prex[i-1];
if(x[i-1]=='H')prex[i]++;
}
cin>>q;
for (int i=1;i<=q;++i){
int l1,r1,l2,r2,len1,len2,h1,h2;
cin>>l1>>r1>>l2>>r2;
len1=r1-l1+1,len2=r2-l2+1;
h1=preh[r1]-preh[l1-1];
h2=prex[r2]-prex[l2-1];
len1-=h1,len2-=h2;
h1+=len1*2,h2+=len2*2;
if(abs(h1-h2)%3==0){
cout<<"YES"<<endl;
}
else cout<<"NO"<<endl;
}
return 0;
}
G. 随机汉堡店




这里题目描述其实有点问题,至少包含 p 个食材,而不是至少包含 p 个不同的食材,所以这道题生菜-生菜 汉堡是合理的,不会被投诉(此处歧义)。
如果贪心,每次压缩最前面和最后面的,那会漏情况。
比如:1 1 2 1 2。这样最后一个2就压不掉了,所以这道题需要动态考虑,每个点到底和谁压缩。
那就直接动态规划。
定义 dp[i] 为使用前 i 个食材可以组成的最大美味总和。
如果不使用第 i 个,显然 dp[i]=dp[i-1]
如果使用第 i 个,显然从 1~i-1 里面找到食材和 i 一样的,压缩在一起,取最大的那个。
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
ll n,k,p,t[1000001],d[1000001],dp[1000001],prefix[1000001];
vector<ll>a[1000001];
int main(){
cin>>n>>k>>p;
for (ll i=1;i<=n;++i)scanf("%lld",&t[i]),a[t[i]].push_back(i);
for (int i=1;i<=n;++i)scanf("%lld",&d[i]),prefix[i]=prefix[i-1]+d[i];
for (ll i=1;i<=n;++i){
dp[i]=dp[i-1];
for(auto j:a[t[i]]){
if(j==i)break;
if(i-j+1>=p){
dp[i]=max(dp[i],dp[j-1]+prefix[i]-prefix[j-1]);
}
}
}
cout<<dp[n];
return 0;
}
注意这道题输入比较多,如果没有ios的cin会超时。
因为提交了一下就过了,所以不需要再优化内部循环的取最大值。
tip:优化具体看评论区,单调队列来优化是错的。
H. 密码是多少?
模拟签到。
J. 开着我的小新车
这里给个思路,没有正式做出来,题面好多错误。



题意槽点(可以跳过往下拉看题目思路)
1、题目保证 xi-xi-1<=L,结果样例里面就出现了一个xi-xi-1>L的情况,然而解释却是不会影响做法。
如果有人使用ST表倍增这种全局考虑的算法,那这道题就爆了,所以这个保证了一个寂寞。。
2、题目说新车续航最多2L,超过L就要休息一天,不打算压榨新车,要爱护汽车来开,啥意思,不能用2L?开了2L就是压榨了吗?。。
根据样例得出结论1 3 6 13 15 18 19 29,L=10,如果第一次开2L,我们可以到达19,休息一天,然后开L,一共三天就能到达目的地,然而样例给的答案是4天,所以这道题不能使用2L,也就是使用L才是保护电池的做法。
3、车只能停在x序列上的具体点上,不能停在野区。
如:1 3 6 13 15 18 19 29,L=10,最右边和最左边差值28,如果可以停在非x序列的位置上,那么依旧是3天就可以完成,1+30=31>29,第四天就回家了,花费3天,所以得出结论,车只能停在x序列的点上。
思路分析
由于比赛结束了不能提交,补题可能要后续压力主办方搞渠道弄开。
因为每个点开L之后经过的点数量不一样,所以我们预处理一下。
定义 dp[i] 为,在 i 点往右开 L 距离能到达的下一个点。

然后我们就可以根据dp写出一个暴力往后跳的代码

然而这样复杂度可能会被卡成n*n,所以想办法跳的时候智能一点,或者说是否有些步骤重复跳了。
可以发现假如1->3->5->7,这样访问的时候 l 从 1,3,5,7任意一个都可以在这条链上二分 r 的位置来找到答案。
如果2->3 链到了原来的链上,那么后面肯定也是一样的,即2->3->5->7
所以 2 开头的在这上面二分查找几个,而我们修改链的操作是O(1)
如果后面都不一样 2->4->6,也不会影响总复杂度,可以发现每个元素最多被选中一次。
考虑对询问的 l,r以 l 进行升序排序,然后构造合法的链,或者修改即可。
总复杂度nlogn,应该可以通过。
(不能补题就放个思路算了)
暴力代码参考
#include <bits/stdc++.h>
#define int long long
#define per(i,j,k) for(int (i)=(j);(i)<=(k);++(i))
#define rep(i,j,k) for(int (i)=(j);(i)>=(k);--(i))
#define debug(a) cout<<#a<<"="<<a<<endl
#define all(x) x.begin(),x.end()
#define EX exit(0)
#define fr first
#define se second
#define endl '\n'
using namespace std;
using ll=long long;
void solve(){
int n;
cin>>n;
int x[n+1];
per(i,1,n)cin>>x[i];
int L;
cin>>L;
int dp[n+1];
per(i,1,n){
//当前在x[i]
//找到x[i]+L能到达的最远距离(用二分,不然复杂度爆了)
int val=x[i]+L;
int l=i+1,r=n;
while(l<r){
int mid=(l+r)>>1;
if(x[mid]>val){//无法到达 mid舍弃
r=mid-1;
}else l=mid;//则l为最终答案
if(l==r-1){//极限只会取左边,再判断一下r是否可行就退出
if(x[r]<=val)l=r;
break;
}
}
//l就是能达到的最远距离
dp[i]=l;
}
int q;
cin>>q;
per(i,1,q){
int l,r;
cin>>l>>r;
if(l>r)swap(l,r);
int now=l;
int res=1;
while(dp[now]<r)now=dp[now],res++;
cout<<res<<endl;
}
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr);
int t=1;
while(t--)solve();
return 0;
}
测试赛
B. IMissYou!
求和输出就行了,字符串容易看走眼打错,直接复制题目的。
#include <bits/stdc++.h>
//#define int long long
#define per(i,j,k) for(int (i)=(j);(i)<=(k);++(i))
#define rep(i,j,k) for(int (i)=(j);(i)>=(k);--(i))
#define debug(a) cout<<#a<<"="<<a<<endl
#define all(x) x.begin(),x.end()
#define EX exit(0)
#define fr first
#define se second
#define endl '\n'
using namespace std;
using ll=long long;
void solve(){
int a[8];
a[0]=0;
per(i,1,7)cin>>a[i],a[0]+=a[i];
if(a[0]>0){
cout<<"IMissYou!"<<endl<<a[0];
}else{
cout<<"OvO";
}
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr);
int t=1;
while(t--)solve();
return 0;
}
A. Jargonless

累计方案数很容易想到,对于S中所有存在的T,均取头和尾的位置相乘,就是当前合法T的所有方案。
但是这样会重复计算,比如头去掉0个和尾去掉0个,下一个也会去掉头0个和尾0个。
给出一个显然的结论:
假如第一个合法的T在S中的位置是,L~R,那么下一个合法T的位置,一定从L+1开始,且一定没有R-1。
所以当头移除1~3位置对上4~5的时候
下一个字符串移除1~3位置也一定只能对上4~5的位置,不会有1~3对上3~5,所以前一个位置的L就不需要了,我们只需要累计当前newL~L之间的新长度即可。
那么考虑移动L的左端点,求差值即可去除重复。
#include <bits/stdc++.h>
#define int long long
#define per(i,j,k) for(int (i)=(j);(i)<=(k);++(i))
#define rep(i,j,k) for(int (i)=(j);(i)>=(k);--(i))
#define debug(a) cout<<#a<<"="<<a<<endl
#define all(x) x.begin(),x.end()
#define EX exit(0)
#define fr first
#define se second
#define endl '\n'
using namespace std;
using ll=long long;
void solve(){
string s,t;
cin>>s>>t;
s="0"+s;
t="0"+t;
// 123
// i j
int l=0,ans=0;
per(i,1,s.length()-1){
if(s[i]==t[1]){
int now=2;
per(j,i+1,s.length()-1){
if(s[j]==t[now])now++;
if(now==t.length()){
ans+=(i-l)*(s.length()-1-j+1);
l=i;
break;
}
}
}
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr);
int t=1;
while(t--)solve();
return 0;
}
更多推荐

所有评论(0)