比赛链接
第一题 三带一
考察知识点:简单模拟
依据题目要求我们只需要看是不是有3个一样的就好了我们只需要对给定的字符串排序即可,然后判断最长连续的串长度是不是3
时间复杂度:O(1)
void solve(){
string s; cin>>s;
sort(s.begin(),s.end());
int ans=1,len=1;
for(int i=1;s[i];i++){
if(s[i]==s[i-1]) len++;
else len=1;
ans=max(ans,len);
}
cout<<(ans==3 ? "Yes" : "No")<<endl;
return ;
}
第二题 数树数
考察知识点:基本树的知识
我们依照基本知识,按照题目要求的方式来编号的话,对于一个点的左端点就是2u-1,右端点就是2u,所以简单模拟即可
时间复杂度:O(n* q * s)
void solve(){
cin>>n>>m;
while(m--){
string s; cin>>s;
int now=1;
for(int i=0;s[i];i++){
if(s[i]=='L') now=now*2-1;
else now*=2;
}
cout<<now<<endl;
}
return ;
}
第三题 分组
考察知识点:二分
我们可以有一个明显的策略就是先排序,然后让相邻的分到一组这样的极差一定是最小的,然后至于极差的大小我们可以使用二分,然后来判断这个时候需要分成的组数,多了肯定不行,少了肯定可以,可以领出来单独一组
时间复杂度:o(nlogn)
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
auto check = [&](int mid)->bool{//lambda写法 写全局可行
int now=a[1];
int cnt=1;
for(int i=1;i<=n;i++){
if(a[i]-now>mid){
cnt++;
now=a[i];
}
}
return cnt<=m;
};
int l=0,r=1e9;// 注意左边界
while(l<r){
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
return ;
}
第四题 健身
考察知识点:背包问题
依照里面意思我们可以直接把每一个连续的区间找出来,然后我们对每一个区间找最优解求和即可找最优解的过程我们使用完全背包来不停的更新最优解即可(完全背包可见基础课)
时间复杂度:O(k*n)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define enl "\n"
const int N = 1000010,mod=1e9+7;
typedef long long LL;
int n,m,q;
LL w[N],sum[N];
LL dp[N];// 表示长度为i的时候最多的增益
void solve(){
cin>>n>>m>>q;
for(int i=1;i<=q;i++){
int x; cin>>x;
sum[x]++;
}
vector<int> res;
for(int i=1;i<=n;i++){
int l=i,r=i;
if(!sum[i]){
while(!sum[r] and r<=n) r++;
res.push_back(r-l);// 找出每一段的长度
i=r-1;
}
}
while(m--){
int s,v; cin>>s>>v;
w[s]=max(w[s],v);
}
// 表示二进制的使用
for(int i=1;i<=n;i++){
for(int j=0;j<=20;j++){
if(i>=(1<<j)){
dp[i]=max(dp[i],dp[i-(1<<j)]+w[j]);
}
}
}
int ans=0;
for(auto&v:res) ans+=dp[v];
cout<<ans<<endl;
return ;
}
第二种状态定义:考虑当为第i天的时候的最大增益是多少
我们可以把天数存起来然后判断当前这个位置能不能放置这个"物品",然后不断更新即可
时间复杂度:O(k*n)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define enl "\n"
const int N = 1000010,mod=1e9+7;
typedef long long LL;
int n,m,q;
LL w[N],sum[N];
LL dp[N];// 表示第i天的时候最多有多少锻炼值
void solve(){
cin>>n>>m>>q;
for(int i=1;i<=q;i++){
int x; cin>>x;
sum[x]++;
}
for(int i=1;i<=n;i++) sum[i]+=sum[i-1];// 这样就可以考虑分段的作用了
while(m--){
int s,v; cin>>s>>v;
w[s]=max(w[s],v);
}
// 表示二进制的使用
for(int i=1;i<=n;i++){
dp[i]=dp[i-1];
for(int j=0;j<=20;j++){
int day=i-(1<<j);
if(day>=0 and sum[i]-sum[day]==0) dp[i]=max(dp[i],dp[day]+w[j]);// 是不是有这样一段空的长度
else break;
}
}
cout<<dp[n]<<endl;
return ;
}
第五题 契合匹配
考察知识点:破环成链+(kmp or hash)
首先我们是一个字符是大字母,另一个是字母的,所以我们可以把一个串当作操作串,把他的大小写对换一下,接着我们是一个环再旋转所以我们考虑直接变为2倍接到后面这就是可以取出来旋转之后的串
算法1.kmp:我们对一个串当作模板串一个串当作操作串然后直接考试使用kmp算法找子串即可
时间复杂度:o(n)
string a,b;
int ne[N];
int ans=INT_MAX;
bool ok=false;
void kmp(string a,string b){
for(int i=2,j=0;i<=n;i++){//kmp模板找操作串
while(a[i]!=a[j+1]&&j) j=ne[j];
if(a[i]==a[j+1]) j++;
ne[i]=j;
}
for(int i=1,j=0;i<=2*n;i++){// kmp模板找匹配串
while(j&&b[i]!=a[j+1]) j=ne[j];
if(b[i]==a[j+1]) j++;
if(j==n){
ok=true;
ans=min({ans,abs(i-n),2*n-i});
}
}
}
void solve(){
cin>>n>>a>>b;
for(int i=0;a[i];i++){
a[i] = isupper(a[i]) ? tolower(a[i]) : toupper(a[i]);
}//操作串
a=a+a;
a=' '+a; b=' '+b;
kmp(b,a);
if(ok){
cout<<"Yes\n"<<ans<<endl;
}
else cout<<"No\n"<<endl;
return ;
}
算法2.字符串hash
我们使用hash之后可以直接从操作串中取出来一段O(1)来判断是不是和指定串匹配,我们只需要对操作串弄出来他的hash数组,以及模板串的hash值来匹配即可
时间复杂度:O(n)
const int N = 2000010,mod=1e9+7,base=131;
typedef unsigned long long ULL;
int n,m,q;
string a,b;
ULL p[N],h[N];
int ans=INT_MAX;
bool ok=false;
ULL query(int l,int r){
return h[r]-h[l-1]*p[r-l+1];
}
void my_hash(string a,string b){
p[0]=1;
ULL res=0;
for(int i=1;i<=2*n;i++){
p[i]=p[i-1]*base;
h[i]=h[i-1]*base+a[i-1];
if(i<=n) res=res*base+b[i-1];
}
for(int i=n;i<=2*n;i++){
if(query(i-n+1,i)==res){
ok=true;
ans=min({ans,abs(i-n),2*n-i});
}
}
}
void solve(){
cin>>n>>a>>b;
for(int i=0;a[i];i++){
a[i] = isupper(a[i]) ? tolower(a[i]) : toupper(a[i]);
}
a=a+a;
my_hash(a,b);
if(ok){
cout<<"Yes\n"<<ans<<endl;
}
else cout<<"No\n"<<endl;
return ;
}
第六题:奇怪的线段
考察知识点:树状数组
PS:对于线段的区间查询类型操作,如果需要数据结构的话优先思考树状数组接着才是线段树等其他的
首先我们考察的是满足有a但是没有b的区间,(注意数据范围中没有标注a,b的大小关系)
我们可以转化这个问题
1.如果a==b就是0
2.如果a < b那就是求再[0,b)这个区间里的包含a的区间个数
3.否则就是[a,n)的区间内a的个数
我们不妨化简题目把a所在区间个数求出来,这个时候我们可以使用前缀和求出包含a的区间个数,接着我们只需要减去在这其中包含b的区间就可以了,接着我们不妨固定现在要求的左端点,我们需要把满足左端点l<=a,右端点r>=b的区间的个数减去,这个时候就需要通过对目标区间排序来实现了,我们优先按照右端点(是否固定到b)固定,然后看左端点越小越好(这样覆盖的区间也就越大),判断是不是需要查询的区间,综合起来即可
时间复杂度:O(nlogn)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define enl "\n"
const int N = 1000010,mod=1e9+7;
typedef long long LL;
int n,m,q;
struct code{
int l,r;
int id,op;
bool operator<(const code&t)const{// 核心就是排序
if(r==t.r){
if(l!=t.l) return l<t.l;
return op<t.op;
}
return r>t.r;
}
}node[N];
int sum[N],res[N];
struct BET{// 封装起来的树状数组
int tr[N];
int inline lowbit(int x){
return x&(-x);
};
void add(int k,int x){
for(int i=k;i<N;i+=lowbit(i)) tr[i]+=x;
};
int query(int k){
int res=0;
for(int i=k;i;i-=lowbit(i)) res+=tr[i];
return res;
};
}tree;
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++){
int l,r; cin>>l>>r;
sum[l]++,sum[r+1]--;
node[i]={l,r,0,0};
}
// 前缀和求出包含a的区间个数
for(int i=1;i<N;i++) sum[i]+=sum[i-1];
for(int i=1;i<=m;i++){
int l,r; cin>>l>>r;
res[i]=sum[l];
if(l>r) swap(l,r);
node[++n]={l,r,i,1};
}
sort(node+1,node+1+n);
for(int i=1;i<=n;i++){
auto [l,r,id,op]=node[i];
if(op==0){
tree.add(l,1);
}
else{
res[id]-=tree.query(l);
}
}
for(int i=1;i<=m;i++) cout<<res[i]<<endl;
return ;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
solve();
}