【补题】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;
}
posted @ 2023-05-17 23:34  文艺平衡树  阅读(133)  评论(0编辑  收藏  举报