进阶训练赛(二十)

目录

问题 A: 计算矩阵边缘元素之和

问题 B: 图像旋转

问题 C: 最长最短单词

问题 D: 输出亲朋字符串

问题 E: [蓝桥杯2022初赛] 李白打酒加强版

(1)朴素版dfs

(2)dfs+记忆化搜索+剪枝

问题 F: 【蓝桥杯2022初赛】扫雷

问题 G: 今天猛犸不上班

问题 H: 你干嘛 哈哈嗨呦

问题 I: 打印文件

问题 J: 群聊交友

问题 K: 字符串查询

问题 L: X老师的多米诺骨牌


问题 A: 计算矩阵边缘元素之和

模拟

void solve()
{
    int n,m;
    cin>>n>>m;
    int ans = 0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            int e;
            cin>>e;
            if(i==n || i==1 || j==1 || j==m) ans+=e;
        }
    }
    cout<<ans;
}
 

问题 B: 图像旋转

注意:输入矩阵不一定都是方阵

int a[105][105];
void solve()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
 
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            cout<<a[n-j+1][i]<<" ";
        }
        cout<<endl;
    }
 
}

问题 C: 最长最短单词

题目难处理的地方在于还有逗号分割,不可以直接用cin读入。具体做法为一直更新最大(小)单词长度和他们最后一个字符的位置即可

void solve()
{
    string s;
    getline(cin,s);
    int len = s.length();
    int minn = 2e9,maxn = -1;
    int idx_max,idx_min;//最大最小值下标
    int now = 0;
    for(int i=0;i<len;i++){
        if(s[i]==' '||s[i]==','){
            if(now>maxn){
                maxn = now;
                idx_max = i;
            }
            if(now<minn){
                minn = now;
                idx_min = i;
            }
            now = 0;
        }else{
            now++;
        }
    }
    for(int i=idx_max-maxn;i<idx_max;i++) cout<<s[i];
    cout<<endl;
    for(int i=idx_min-minn;i<idx_min;i++) cout<<s[i];
 
 
}

问题 D: 输出亲朋字符串

模拟

void solve()
{
    string s;
    cin>>s;
    int len = s.length();
    for(int i=0;i<len-1;i++){
      cout<<char(s[i]+s[i+1]);
    }
    cout<<char(s[0]+s[len-1]);
 
 
}

问题 E: [蓝桥杯2022初赛] 李白打酒加强版

(1)朴素版dfs

n+m最大能达到200,如果直接用dfs暴力会超时

直接写dfs思路很好想,但是会超时,复杂度为O(2^n)

int n,m;
int ans;
void dfs(int u,int cnt,int a,int b){
    //u为当前酒还剩多少升 cnt为当前层数 a为选了几个店,b为选了几次花
    if(a>n || b>m) return ;
    if(cnt==n+m-1){ //最后一次必须为花,且酒需要剩下一升
        if(u == 1 && a==n && b==m-1) ans++;
        return ;
    }
    if(u<=0) return ;
    dfs(u*2,cnt+1,a+1,b);
    dfs(u-1,cnt+1,a,b+1);
}

void solve()
{
    int t;
    cin>>t;
    while(t--){
        cin>>n>>m;
        ans = 0;
        dfs(2,0,0,0);
        cout<<ans%mod<<endl;
    }
}

想用dfs不超时的话就需要用到记忆化搜索和剪枝了,减少不必要的递归,假设u是酒量,剪枝之后 u 的值又不会大于 m

1.题目限定最后一次遇到的是花且遇到花时一定有酒,那么当u=0时,n,m一定也为0,(n=0是因为最后一次遇花,m=0是因为遇到必须要有酒)

2.当然,如果u>m时,说明酒喝不完,就满足不了题目提到的酒正好喝完这个条件

3.同理,n>=m时也不符合题意,因为酒的增长速度是指数级别的,而遇到花喝酒,酒的减少速度是线性的

(2)dfs+记忆化搜索+剪枝

int vis[100+5][100+5][100+5];
ll dfs(int n,int m,int u){
    //u为当前酒还剩多少升 a为剩几个店可以选择,b为剩多少花能选
    if(u==0) return  (n==0 && m==0);
    if(u>m || n>=m) return 0;
    if(n==0) return u==m;
    if(vis[n][m][u]!=-1) return vis[n][m][u];
    ll ans = dfs(n,m-1,u-1) + dfs(n-1,m,u*2);
    ans %= mod;
    vis[n][m][u] = ans;
    return ans;
}

void solve()
{
    int t;
    cin>>t;
    while(t--){
        memset(vis,-1,sizeof vis);
        int n,m;
        cin>>n>>m;
        cout<<dfs(n,m,2)<<endl;
    }
}

问题 F: 【蓝桥杯2022初赛】扫雷

dfs+哈希表

const int M = 999997;//大于n+m的100000数据量够用了
const int base = 1e9 + 1;//保证坐标不会重复
int myhash[M];//散列表
int cnt[M];//该点有几个炸弹
int maxr[M];//该点炸弹的最大半径
bool vis[M];//该点炸弹是否已经引爆
int ans;
 
int getkey(int x,int y)
{
    //坐标转移成一个数
    return x*base+y;
}
 
int findaddress(int x)
{
    //返回数值x在散列表中的索引
    int t = (x%M+M)%M;
    while(myhash[t]!=-1 && myhash[t]!=x){//第一个条件针对存放值+第二个条件针对取值
        t++;
        if(t==M) t=0;
    }
    return t;
}
 
bool dist(int x1,int y1,int x2,int y2,int r)
{
    return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)<=r*r;
}
 
void dfs(int x,int y,int r)
{
    for(int i=x-r;i<=x+r;i++){
        for(int j=y-r;j<=y+r;j++){
            int key = getkey(i,j);
            int idx = findaddress(key);
            if(cnt[idx] && !vis[idx] && dist(i,j,x,y,r)){
                ans+=cnt[idx];
                vis[idx] = true;
                dfs(i,j,maxr[idx]);
            }
        }
    }
}
void solve()
{
    int n,m;
    cin>>n>>m;
    memset(myhash,-1,sizeof myhash);
    for(int i=0;i<n;i++){
        int x,y,r;
        cin>>x>>y>>r;
        int key = getkey(x,y);
        int idx = findaddress(key);
        myhash[idx] = key;
        cnt[idx]++;
        maxr[idx] = max(r,maxr[idx]);
    }
    for(int i=0;i<m;i++){
        int tx,ty,tr;
        cin>>tx>>ty>>tr;
        dfs(tx,ty,tr);
    }
    cout<<ans<<endl;
}

问题 G: 今天猛犸不上班

和求某个数在n进制下一样的求法

void solve()
{
    int n,m;
    cin>>n>>m;
    int digit = 0;
    while(n){
      n/=m;
      digit++;
    }
    cout<<digit;
}

问题 H: 你干嘛 哈哈嗨呦

如果人数是奇数,公鸡摆放最优的位置是在中间人的位置上;如果人数是偶数,最优的位置在中间两个人坐标的平均数、平均数-1两个数中的一个(int默认向下取整了)

const int N = 100+5;
int a[N];
void solve()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    sort(a+1,a+n+1);
    int ans = 0;
    if(n&1){
        int x = a[n/2+1];
        for(int i=1;i<=n;i++) ans+=(x-a[i])*(x-a[i]);
    }else{
        int x1 = (a[n/2]+a[n/2+1])/2; 
        int x3 = (a[n/2]+a[n/2+1])/2-1;
        int ans1 = 0,ans2 = 0,ans3 = 0;
        for(int i=1;i<=n;i++){
            ans1+=(x1-a[i])*(x1-a[i]);
            ans3+=(x3-a[i])*(x3-a[i]);
        }
        ans = min(ans1,ans3);
    }
    cout<<ans<<endl;
}

问题 I: 打印文件

也是涉及取整的问题

void solve()
{
    double n;
    cin>>n;
    cout<<(int)(n/2+0.5);
 
}

问题 J: 群聊交友

可以把这题抽象成一个图论问题。

N个群友视为n个独立的点,之间的联系通过建双向边完成。

逐条翻译条件:
(1)图中不存在自环

(2)在朋友关系的图中,i,j之间不存在边

(3)在黑名单关系的图中,i,j之间不存在边

(4)i,j是在一个连通分量中的

1/2/3条件比较容易实现,存储图即可,条件4查找两个点是否在同一个连通分量中,可以使用并查集+启发式合并实现

const int N = 100000+10;
int n,m,k;
int p[N];//记录祖宗节点
int _size[N];//记录联通分量中点的个数
vector<int>g1[N],g2[N];//g1为朋友,g2为黑名单

int find(int x) //查找祖宗节点
{
    if(x==p[x]) return x;
    return p[x] = find(p[x]);
}

void solve()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++) p[i] = i,_size[i] = 1;
    for(int i=0;i<m;i++){
        int l,r;
        cin>>l>>r;
        g1[l].push_back(r);
        g1[r].push_back(l);
        int fa = find(l);
        int fb = find(r);
        if(_size[fa]<_size[fb]) swap(fa,fb);
        if(fa!=fb){
            _size[fa] += _size[fb];
            p[fb] = fa;
        }
    }
    for(int i=0;i<k;i++){
        int l,r;
        cin>>l>>r;
        g2[l].push_back(r);
        g2[r].push_back(l);
    }

    for(int i=1;i<=n;i++){
        int t = find(i);
        int res = _size[t]-1;//连通分量中有几个朋友
        //cout<<res<<endl;
        for(auto it:g1[i]){//去除在连通分量中是朋友的
            if(find(it)==t) res--;
        }
        for(auto it:g2[i]){//去除在连通分量中是黑名单的
            if(find(it)==t) res--;
        }
        cout<<res<<" ";
    }
}

问题 K: 字符串查询

这题和最近cf中一场Div4里面的题目很像。原题链接:Problem - F - Codeforces

我们可以将每个小写字母出现的下标存储在set中,计算区间中有多少种字母时,只需要计算有多少个小写字母的下标在区间范围内即可,可以用二分查找下标位置。

例如:set['z'-'a']中有元素1,4,8,代表s[1],s[4],s[8]的元素是小写字母z

操作1:将下标加入对应字母的set容器中

操作2:将被修改的字母从对应容器中删去,修改后的字母加入对应字母的容器中

const int N = 500000+10;
char s[N];
set<int>se[26+10];
void solve()
{
    int n;
    cin>>n>>s+1;
    for(int i=1;i<=n;i++) se[s[i]-'a'].insert(i);
    int q;
    cin>>q;
    while(q--){
        int op;
        cin>>op;
        if(op==1){
            int idx;
            char c;
            cin>>idx>>c;
            if(c!=s[idx]){
                se[s[idx]-'a'].erase(idx);
                se[c-'a'].insert(idx);
                s[idx] = c;
            }
        }else{
            int l,r;
            cin>>l>>r;
            int res = 0;
            for(int i=0;i<26;i++){
                auto t = se[i].lower_bound(l);//找到第一个大于等于l的位置
                if(t!=se[i].end() && *t<=r) res++;
            }
            cout<<res<<endl;
        }
    }

}

问题 L: X老师的多米诺骨牌

Orz,源于出题人的题解(周赛的题)

思路:首先,将 pair根据 Xi 的大小进行排序。然后我们考虑动态规划,当只考虑第 i 个骨牌和 i 之后的骨牌时,可能的剩余骨牌状态数。那么,如果骨牌 i 没有被 推到,那么可能的状态数为 dp[i+1];如果骨牌 i 被推倒那么可能的状态数为 dp[Ri]( Ri= 只有骨牌 i 推到后,大于 i 中最小的未被推到的骨牌的编号(如果没有则 Ri=n+1)),所以 dp[i]=dp[i+1]+dp[Ri](dp[n+1]=1)。之后我们可以使用单调队列优化 dp,i 从 n 到 1 得出 Ri;或者用二分+求区间最大值也可以求出 Ri。

const int N = 2e5+10;
const int mod = 998244353;
int n;
PII a[N];
int dp[N];
stack<PII>sta;
void add(int &x,int y)
{
    x+=y;
    if(x>=mod) x-=mod;

}
void solve()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i].first>>a[i].second;
    sort(a+1,a+1+n);
    dp[n+1] = 1;
    for(int i=n;i>=1;i--){
        int tmp = a[i].first+a[i].second;
        int R = i+1;
        while(!sta.empty() && tmp>a[sta.top().first].first){
            R = sta.top().second;
            sta.pop();
        }
        sta.push({i,R});
        dp[i] = dp[i+1];
        add(dp[i],dp[R]);
    }
    cout<<dp[1];
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届程序设计竞赛(同步赛)题解“中国东信杯”广西大学第四届
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值