【补题】2023 CCPC河南省赛
题目链接
https://codeforces.com/gym/104354
A. 小水獭游河南
签到题。
初看有点吓人,跟回文串有关,会不会是PAM啥的,然后是大水题。。。
容易发现A串的约束非常强,没有重复字符,意味着A串的长度最大也就是26。我们枚举A串,同时看剩余的后缀是不是回文串就行了。
时间复杂度\(\Theta(26n)\)
代码:
点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define maxn 100010
using namespace std;
char s[maxn];
int c[26];
int check(int l,int r){
int flag=1,i,j;
for(i=l,j=r;i<=j;++i,--j){
if(s[i]!=s[j]){
flag=0;
break;
}
}
return flag;
}
int main(){
int i,j,n,T,flag,k;
char last;
// freopen("test.in","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%s",s);
flag=0;
for(i=0;i<26;++i) c[i]=0;
n=strlen(s);
last=s[n-1];
for(i=0;i<n;++i){
if(i>0&&s[i]==last){
if(check(i,n-1)){
flag=1;break;
}
}
c[s[i]-'a']++;
if(c[s[i]-'a']>1) break;
}
if(flag) printf("HE\n");
else printf("NaN\n");
}
return 0;
}
B. Art for Rest
实现很简单,但是感觉还是难以一眼看出做法,不知道为啥过的人这么多。
考虑暴力,我们直接枚举块长,然后对于已经分好块的数组,检验是否满足条件。
当然模拟排序过程是不可取的,我们不如考虑什么情况下不可能满足条件。
简单思考一下发现,块必须是块间“整体有序”的,也就是说,如果我们一个一个块扫过去,不能出现当前块中某个值比前面块中某个值小的情况。
然后我们只要维护前缀最大值,查询区间最小值,比较即可。
由于数据较大,推荐使用\(O(1)\) 查询的ST表实现RMQ。
等等,你外面不还有一层循环吗,总共两层循环复杂度珂以接受吗?
注意到我们内层循环的次数是\(\lfloor \frac{n}{k}\rfloor\)的,根据调和级数求和的结论知:最终复杂度是\(\Theta(n log n)\)的。
代码:
点击查看代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define maxn 1000010
using namespace std;
int a[maxn];
int mx[maxn],mn[maxn][26];
int log_2[maxn];
int query(int l,int r){
int i=log_2[r-l+1];
return min(mn[l][i],mn[r-(1<<i)+1][i]);
}
int main(){
int i,j,n,m;
int ans=0;
// freopen("test.in","r",stdin);
scanf("%d",&n);
for(i=1;i<=n;++i) scanf("%d",&a[i]);
for(i=1;i<=n;++i) mn[i][0]=a[i];
log_2[1]=0;
for(i=2;i<=n;++i) log_2[i]=log_2[i>>1]+1;
for(i=1;i<=log_2[n];++i){
for(j=1;j+(1<<i)-1<=n;++j){
mn[j][i]=min(mn[j][i-1],mn[j+(1<<i-1)][i-1]);
}
}
for(i=1;i<=n;++i) mx[i]=max(mx[i-1],a[i]);
for(i=1;i<=n;++i){
int flag=1;
for(j=1;j<=n;j+=i){
int l=j,r=min(j+i-1,n);
int t=query(l,r);
if(t<mx[j-1]){
flag=0;
break;
}
}
ans+=flag;
}
printf("%d",ans);
return 0;
}
C. Toxel 与随机数生成器
简单题,胆子大就能过。
题目还是挺新颖的(也许是我孤陋寡闻了),初见容易被吓到。
注意观察题目关键词和数据范围,如果他使用的是假的随机程序,那么一定会出现若干重复的模式,而且这些重复串是随机序列的前缀。
那么,假如这样的重复模式存在,它一定是串\(S\)的某个前缀。
然后观察题意,假的随机程序,它刷新种子的间隔不会小于\(1000\),也就是说重复串长度\(\geq 1000\)。
那我们不妨就令\(|t|=1000\),即串\(t\)是\(s\)的长度为\(1000\)的前缀,然后以\(t\)为模版串做\(KMP\)字符串匹配,如果多次匹配上,那么有很大概率是假的随机程序。
诶,但是题目可没说分段长度是多少啊,它甚至不是定长?
但是我们知道的是,无论何种情况,只要它是假的随机程序,\(t\)一定是每一个分段的前缀,我们得到的匹配次数一定只多不少。
为了提高我们玄学程序的正确率,我们再在“多次”上做做文章。分段长度有上界\(10000\),\(|s|=1000000\),那么分段数至少有\(100\),如果我们加上匹配次数\(\geq 100\)的判定,正确率就更高了。
代码:
点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define maxn 1000010
#define len 1000
using namespace std;
char s[maxn],t[maxn];
int nxt[maxn];
void init(int n){
int i,j=0;
for(i=2;i<=n;++i){
while(j&&t[j+1]!=t[i]) j=nxt[j];
if(t[j+1]==t[i]) ++j;
nxt[i]=j;
}
}
int match(int m,int n){
int i,j=0,cnt=0;
for(i=1;i<=n;++i){
while(j&&t[j+1]!=s[i]) j=nxt[j];
if(s[j+1]==s[i]) j++;
if(j==m) cnt++,j=nxt[j];
}
return cnt;
}
int main(){
int i,j,n,k;
// freopen("test.in","r",stdin);
scanf("%s",s+1);
n=strlen(s+1);
for(i=1;i<=len;++i) t[i]=s[i];
init(len);
k=match(len,n);
if(k>=100) printf("No\n");
else printf("Yes\n");
return 0;
}
E. 矩阵游戏
简单题。
看上去非常DP,实际上也非常\(DP\)。
好像玄学卡空间?滚动数组优化掉一维就好了。
(不知道为什么这么水的题过的不多qwq)
代码:
点击查看代码
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
char a[510][510];
int dp[2][510][1010];
int main(){
int i,j,k,n,m,T,x,ans;
// freopen("test.in","r",stdin);
ios::sync_with_stdio(false);
cin>>T;
while(T--){
cin>>n>>m>>x;
for(i=1;i<=n;++i)
for(j=1;j<=m;++j)
cin>>a[i][j];
for(i=1;i<=m;++i)
for(j=0;j<=x;++j)
dp[0][i][j]=dp[1][i][j]=0;
for(i=1;i<=n;++i)
for(j=1;j<=m;++j)
for(k=0;k<=x;++k){
if(a[i][j]=='1') dp[i&1][j][k]=max(dp[(i&1)^1][j][k],dp[i&1][j-1][k])+1;
else if(a[i][j]=='?'){
if(k){
int t1=max(dp[i&1][j-1][k],dp[i&1][j-1][k-1]+1);
int t2=max(dp[(i&1)^1][j][k],dp[(i&1)^1][j][k-1]+1);
dp[i&1][j][k]=max(t1,t2);
}
else dp[i&1][j][k]=max(dp[i&1][j-1][k],dp[(i&1)^1][j][k]);
}
else{
dp[i&1][j][k]=max(dp[i&1][j-1][k],dp[(i&1)^1][j][k]);
}
}
ans=dp[n&1][m][x];
printf("%d\n",ans);
}
return 0;
}
F. Art for Last
简单题。
注意到结果只与集合有关,与顺序无关,所以我们可以先排好序。
然后观察到最优结果必然是取连续一段(排序后的数组),最大差值就是区间头尾之差,最小差值一定在相邻两个元素间取到。
所以做下差分,在差分数组里找区间最小值即可。
至于为啥一定取连续区间,反证法很好证。
时间复杂度\(\Theta(n log n)\)。
代码:
点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 500010
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
int a[maxn];
int v[maxn<<2],l[maxn<<2],r[maxn<<2];
void update(int x){
v[x]=min(v[x<<1],v[x<<1|1]);
}
void build(int x,int left,int right){
l[x]=left,r[x]=right;
if(left==right){
v[x]=abs(a[left+1]-a[left]);
return;
}
int mid=left+right>>1;
build(x<<1,left,mid);
build(x<<1|1,mid+1,right);
update(x);
}
int query(int x,int left,int right){
int r1=inf,r2=inf;
if(l[x]>=left&&r[x]<=right) return v[x];
int mid=l[x]+r[x]>>1;
if(left<=mid) r1=query(x<<1,left,right);
if(right>mid) r2=query(x<<1|1,left,right);
return min(r1,r2);
}
int main(){
int i,j,n,T,flag,k;
ll ans=-1;
// freopen("test.in","r",stdin);
scanf("%d%d",&n,&k);
for(i=1;i<=n;++i){
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);
for(i=2;i<=n;++i){
if(a[i]==a[i-1]){
printf("0");
return 0;
}
}
build(1,1,n-1);
for(i=k;i<=n;++i){
ll val=(ll)(a[i]-a[i-k+1])*(ll)query(1,i-k+1,i-1);
if(ans<0) ans=val;
ans=min(ans,val);
}
printf("%lld",ans);
return 0;
}
G.Toxel 与字符画
中档题。
感觉是大模拟,懒得写qwq。
思路简单,但是赛时肯定会花较长的时间来写。
H.Travel Begins
签到题。
四舍五入,一定要和\(0.5\)扯上关系,要结果最大就是尽可能多给每个数加\(0.5\)的尾巴,结果最小就加\(0.499999...\)。
注意奇偶情况分讨,以及\(n\)很小的时候的特判即可。
细节稍微有点多,较多队没有1A。
代码:
点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define maxn 100010
using namespace std;
int solvemax(int n,int k){
if(k==1) return n;
if(k>2*n) return n*2;
if(k&1){
return n+(k-1)/2;
}
else{
return n+k/2;
}
}
int solvemin(int n,int k){
if(k==1) return n;
if(k>2*n) return 0;
if(k&1){
return n-(k-1)/2;
}
else{
return n-k/2+1;
}
}
int main(){
int i,j,n,T,flag,k;
char last;
// freopen("test.in","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
printf("%d %d\n",solvemin(n,k),solvemax(n,k));
}
return 0;
}
K.排列与质数
中档题。
没啥特别好的构造策略,我做的时候卡了好久(太弱小了QAQ)。
然后写了个dfs,打表,发现\(>4\)的情况都是有解的。
组织了好久语言还是没法清晰的表达我的思考过程,还是直接放代码吧(嘤嘤嘤)。
代码:
点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 100010
using namespace std;
int a[maxn],n;
int book[maxn];
int b[maxn];
int p[maxn],z[maxn],tot=0;
int tmp[10],used[10],num=0;
int dfs(int step,int N){
int i,ans=0;
if(step>N){
int val=abs(a[N]-a[step%n]);
return z[val]^1;
}
for(i=1;i<=num;++i){
if(used[i]) continue;
int val=abs(tmp[i]-a[step-1]);
if(z[val]) continue;
used[i]=1;
a[step]=tmp[i];
ans|=dfs(step+1,N);
if(ans) return 1;
used[i]=0;
}
return ans;
}
int solve(int l,int r){
return dfs(l,r);
}
int main(){
int i,j,k1,k2,ans;
// freopen("test.in","r",stdin);
// freopen("test.out","w",stdout);
z[1]=1;
for(i=2;i<maxn;++i){
if(!z[i]) p[++tot]=i;
for(j=1;j<=tot&&i*p[j]<maxn;++j){
z[i*p[j]]=1;
if(!(i%p[j])) break;
}
}
scanf("%d",&n);
if(n<=4) printf("-1");
else{
a[1]=book[1]=1;
i=1,j=n+1;
k1=2,k2=3;
while(j-i-1>8){
if(k1&1){
a[++i]=k1;
book[k1]=1;
a[++i]=k1+2;
book[k1+2]=1;
}
else{
a[++i]=k1+2;
book[k1+2]=1;
a[++i]=k1;
book[k1]=1;
}
if(k2&1){
a[--j]=k2;
book[k2]=1;
a[--j]=k2+2;
book[k2+2]=1;
}
else{
a[--j]=k2+2;
book[k2+2]=1;
a[--j]=k2;
book[k2]=1;
}
k1+=4;k2+=4;
swap(k1,k2);
}
for(int k=1;k<=n;++k){
if(!book[k]) tmp[++num]=k;
}
ans=solve(i+1,j-1);
if(ans){
for(i=1;i<=n;++i) printf("%d ",a[i]);
}
else printf("-1");
}
return 0;
}