2022ccpc题解

2023 年第五届河南省 CCPC 大学生程序设计竞赛

Problem A. Mocha 上小班啦

image-20240424124147780

思路:求n个数位的最小值,条件:每一位数字都不同切不含前导零。

只需要把0放到第二位,其他按从小到大输出,大于10以后输出-1即可。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    //预处理每一位的数字
    int a[10]={1,0,2,3,4,5,6,7,8,9};
    int n;
    cin>>n;
    if(n<=10)//小于10的按位输出
    for(int i=0;i<n;i++)cout<<a[i];
    else//大于10的输出-1
    cout<<"-1";
    return 0;
}

Problem E. Serval 的俳句

image-20240424124759523

思路:暴力循环,先找出第一个包含5个相同字母的位置,然后找第二个包含7个相同字母的位置,最后找5个相同字母的位置,若都能找到就输出答案,若当层循环内为找到,则退出并把这一层记录的清空,否则下一次再进入这一层就会使用上一次的记录。

#include <bits/stdc++.h>
using namespace std;
//用桶记录每个字母出现的次数。
int a[26],b[26],c[26];

int main()
{
    int n;
    string s;
    cin>>n>>s;
    for(int i=0;i<n;i++)
    {
        a[s[i]-'a']++;
        //当前字母出现过5次进入下一层循环
        if(a[s[i]-'a']==5)
        {
            for(int j=i+1;j<n;j++)
            {
                //是-'a'不要写成-'0'
                b[s[j]-'a']++;
                //当前字母出现过7次进入下一层循环
                if(b[s[j]-'a']==7)
                {
                    for(int k=j+1;k<n;k++)
                    {
                        c[s[k]-'a']++;
                        //当前字母出现过5次输出结果退出循环
                        if(c[s[k]-'a']==5)
                        {
                        for(int t=0;t<5;t++)cout<<s[i];
                        for(int t=0;t<7;t++)cout<<s[j];
                        for(int t=0;t<5;t++)cout<<s[k];
                        return 0;
                        }
                    }
                    //退出当前循环,清空数组
                    memset(c,0,sizeof(c));
                }
            }
            memset(b,0,sizeof(b));
        }
    }
    //没找到输出"none"
    cout<<"none";
    return 0;
}

Problem F. 集合之和

image-20240424125703250

题意:构造一个集合,使得集合内的元素和等于n;

题解:先拿顺序数组{1,2,3,4,5,6…n}尝试构造,发现只有第一行和最后一列是不充分元素,易观察到构造出集合的数量是2*n+1,涵盖所有奇数,只需再构造一个偶数数组即可,第一列和最后一行的元素包含了第一行和最后一列,可知数量为2和4的构造不出,顺序数组构造出的第一列和最后一行是按大小递增的再{2,n}之间包含了所有大小的数,可以想到若把中间抽出一个数就会有一个空缺,可以放入其他数,试构造{1,3,4,5,6…n}可以发现得到的数据量正好为偶数个。使用这两个数组可以构造出除2,4以外的所有数。

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    //先排除2和4
    if (n == 2 || n == 4) {
        cout << "-1";
        return 0;
    }
    int x;
    if (n % 2 == 1) {//若为奇数则用{1,2,3,...,n}
        x = n / 2 + 1;
        cout << x << endl;
        for (int i = 1; i <= x; i++)
            cout << i << " ";
    } else {//若为偶数则用{1,3,4,...,n}
        x = n / 2;
        cout << x << endl;
        for (int i = 1; i <= x + 1; i++) {
            if (i == 2)
                continue;
            cout << i << " ";
        }
    }
    return 0;
}

Problem G. Mocha 上大班了

image-20240424131517603

思路:诈骗题!如果0&1的话结果为1,只有1&1与结果为1,可知如果某一列出现了0,那么n个字符串按位与运算后必为0,只有每一个字符串同一个位置都为1最后该位置的结果才是1。

#include <bits/stdc++.h>
using namespace std;
int a[5000];
int main()
{
    //先给数组都赋值为1
    for(int i=0;i<5000;i++)a[i]=1;
    int n,m,ans=0;
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
       string s;
       cin>>s;
       for(int i=0;i<m;i++)
       {//如果当前字符串的当前位置出现了0,那么这一位最后与运算后必为0。
        if(s[i]=='0')
        a[i]=0;
       }
    }
    int t;
    cin>>t;
    for(int i=0;i<5;i++)cin>>t;//输入无用的变量
    for(int i=0;i<m;i++)ans+=a[i];//把最后剩下的1都加起来即为数字1的个数和
    cout<<ans;
    return 0;
}

Problem H.旋转水管

image-20240424132316200

思路:dfs搜索,如果是I的话只有一个方向,L的话有两个方向。进入dfs的时候判断是哪个类型的水管在进行对应的判断即可,维护当前进入的方向,便于判断下一步该往哪走。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
char mp[3][N];
//不是st[N][N],会爆内存
int st[3][N];
//定义反向为下右上左
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};
int n, zx, zy,ti;
//x,y为当前位置,dic为当前进入的方向
void dfs(int x,int y,int dic)
{
    //记录当前位置走过
    st[x][y]=1;
    int tx,ty;
    if(mp[x][y]=='I')
    {
        //如果当前位置为I则下一步的方向与当前的方向一致
        tx=x+dx[dic];
        ty=y+dy[dic];
        //管道只有两行
        if(tx==1||tx==2)
        {
            if(ty>=1&&ty<=n&&st[tx][ty]==0)//若下一步位置没走过且不越界则继续往下走,方向不变。
            {
                dfs(tx,ty,dic);
                st[tx][ty]=0;//回溯
            }
        }
        if(tx==3&&ty==zy)//如果下一步的位置为出口这标记为1;
        {
            ti=1;
            return;
        }
    }else
    {//如果当前位置为L则下一步的方向有两种分别为(dic+1)%4或(dic+3)%4,其他思路和上面相同
        tx=x+dx[(dic+1)%4];
        ty=y+dy[(dic+1)%4];
        if(tx==1||tx==2)
        {
            if(ty>=1&&ty<=n&&st[tx][ty]==0)
            {
                dfs(tx,ty,(dic+1)%4);
                st[tx][ty]=0;
            }
        }
        if(tx==3&&ty==zy)
        {
            ti=1;
            return;
        }
        tx=x+dx[(dic+3)%4];
        ty=y+dy[(dic+3)%4];
        if(tx==1||tx==2)
        {
            if(ty>=1&&ty<=n&&st[tx][ty]==0)
            {
                dfs(tx,ty,(dic+3)%4);
                st[tx][ty]=0;
            }
        }
        if(tx==3&&ty==zy)
        {
            ti=1;
            return;
        }
    }
    return;
}

int main() {
    int t;
    cin>>t;
    while(t--)
    {
        //每次循环记得把t跟新为0
        ti=0;
        cin>>n>>zx>>zy;
        for(int i=1;i<=2;i++)
        for(int j=1;j<=n;j++)
        cin>>mp[i][j];//输入管道
        dfs(1,zx,0);//从人口进入
        st[1][zx]=0;//回溯
        if(ti)//如果标记过,即为找到了出口,反之没找到
        cout<<"YES"<<endl;
        else
        cout<<"NO"<<endl;;
    }
    return 0;
}

Problem J. Mex Tree

image-20240424134120780

思路:

我们用权值作为编号:(官方题解)

  • k = 0,那么自然要删掉0号点,会得到若干子树,为了保证连通性,从中选择最大子树即可
  • k = n,最大编号为n-1,所以选所有点即可
  • 0<k<n:

首先选出的子树肯定需要包含0号点。但是去判断位置关系非常麻烦,我们可以直接以0号点作为根节点。

这个时候我们需要删除k号点,那么为了保证0号点的存在,那么我们只能连同k及其子树全部删除,这是唯一的可能合法操作,并且可以得到这个子树大小为n-sizek

那么为了进一步判断合法性,就需要知道k及其子树内,是否存在一个点i,使得i<k。(因为mex=k,那么只要所有小于k的点都不在子树内,剩下未删除的子树的mex就是k)。

这个只需要记录一下子树内的最小值即可。如果最小值就是k,就说明内部没有i<k的节点。

#include <bits/stdc++.h>
using namespace std;
// 范围再1e6!
const int N = 1e6 + 10, M = 2e6 + 10;
int e[M], h[N], ne[M], idx;
int vi[N], mn[N], sz[N], ans[N], n;
//构造连通图
void add(int a, int b) {
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}
//递归整颗树,维护每个点的子树的大小和该子树的最小权值
void dfs(int a, int b) {//a为当前点,b为上一个点
    //每一点的初识长度为1
    sz[a] = 1;
    mn[a] = vi[a];//记录当前点的权值
    for (int i = h[a]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == b)
            continue;//防止自环
        dfs(j, a);//递归下一个点
        sz[a] += sz[j];//计算当前子树的长度
        mn[a] = min(mn[a], mn[j]);//维护当前子树的最小值
    }
}

void dfs1(int a, int b) {
    if (vi[a] != 0 && vi[a] != n) {
        if (vi[a] == mn[a])//若a点的权值等于a子树的最小权值,另一个子树肯定包含小于a点权值的所有点即mek=k
            ans[vi[a]] = n - sz[a];
        else//若a点的权值不等于等于a子树的最小权值,另一个子树肯定不包含小于a点权值的所有点
            ans[vi[a]] = 0;
    }
    for (int i = h[a]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == b)
            continue;
        // 不要写成dfs()
        dfs1(j, a);//进入下一个点判断
    }
}
int main() {
    memset(h, -1, sizeof(h));
    cin >> n;
    for (int i = 1; i <= n; i++)
        scanf("%d", &vi[i]);
    for (int i = 2; i <= n; i++) {
        int j;
        scanf("%d", &j);
        add(i, j);
        add(j, i);
    }
    int root = 0;
    for (int i = 1; i <= n; i++)
        if (vi[i] == 0) {
            root = i;
            break;
        }
    dfs(root, -1);
    ans[n] = n;
    // 不是i++!

    for (int i = h[root]; ~i; i = ne[i]) {
        int j = e[i];
        ans[0] = max(ans[0], sz[j]);//找出除0点以外的最大的子树
    }
    dfs1(root, -1);
    for (int i = 0; i <= n; i++)
        cout << ans[i] << " ";
    return 0;
}

看看就好,不太会

posted @ 2024-04-24 16:59  cker  阅读(126)  评论(0)    收藏  举报