暑期训练赛 BAPC 2014 Preliminary 解题报告

本文精选了几道算法竞赛题目并提供了详细的解题思路与代码实现,涵盖了概率论、图论、动态规划等多个方面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A. Choosing Ice Cream
题目链接:https://nanti.jisuanke.com/t/28201
题意:有n个ice cream,给你一个骰子k个面,问能否公平的(等概率选出一个ice cream)选择一个ice cream。如果能,求出投掷几次(或者需要几个骰子),不能则输出“unbounded”。
思路:
如果能够公平选择(即投掷后每个ice cream等概率出现),则需要满足 n | k ^(i).
那么,n | gcd(n,k).
意思就是,每次需要将n分为几个骰子,每个骰子出现的概率是相等的,(每个骰子上的ice cream也要均分k,因此是选取最大公约数)直到最后只需要一个骰子就能够选出来ice cream,此时,n为1.
这样就保证了每次选取概率都是相等的,相乘得到的最终概率也是等的。
例如
9 3
9个ice cream , 一个骰子3面,公约数3.
那么相当于分了3个骰子,每个骰子n/3 = 3 面,此时一个骰子够用。算法结束

8 2
8个ice 一个骰子2面,公约数2
分2个骰子,一个4面(一个骰子不够,继续分)。
变成了4个ice (对应n/=gcd(n,k)),一个骰子2面,公约数2
分2个骰子,一个2面,结束。

Code:

#include <bits/stdc++.h>
using namespace std;
int gcd( int a , int b ){
    return b == 0 ? a : gcd( b , a % b );
}
int main(){
    int T;
    cin >> T;
    int n , k ;
    while( T-- ){
        cin >> n >> k ;
        int i;
        for( i = 0 ; n > 1 && k > 1 ; i ++ ){
            k = gcd( n , k );
            n /= k;
        }
        if( n > 1 ){
            printf("unbounded\n");
        }else printf("%d\n",i);
    }
    return 0 ;
}

B. Failing Components
题目连接:https://nanti.jisuanke.com/t/28202
题意:给一个初始坏掉的零件,并给出依赖关系,所有依赖坏掉零件的零件也会在ts后坏掉,求坏掉的总个数以及时间。
思路:
一遍bfs,用优先队列按时间排序,每次取最先坏掉的,dis存每个零件到最初坏掉那个的距离(时间),要求这个的最短时间。
最后dis数组不为INF的则能够通过最初坏掉的传过来,即为坏掉的个数。
记录最大的距离即为总共需要的时间。
Code:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int AX = 1e4+66;
int n , m , s ;

struct Node{
    int to ;
    int w  ;
    friend bool operator< ( const Node &x, const Node &y){
        return x.w > y.w;
    }
};
std::vector<Node> v[AX];

int res ,cnt ;
int dis[AX];

void bfs(){
    priority_queue<Node>que;
    Node p;
    p.to = s ;
    p.w = 0 ;
    dis[s] = 0 ;

    que.push(p);
    while( !que.empty() ){
        Node tmp = que.top();
        que.pop();
        int t = tmp.to;

        for( int i = 0 ; i < v[t].size() ; i++ ){
            if( dis[v[t][i].to] > v[t][i].w + dis[t] ){
                dis[v[t][i].to] = v[t][i].w + dis[t];
                Node tt;
                tt.to = v[t][i].to;
                tt.w = dis[v[t][i].to];
                que.push(tt);
            }
        }
    }
}

int main(){
    int T;
    scanf("%d",&T);
    int x ;
    while( T-- ){
        for( int i = 0 ; i <= n ; i++ ) v[i].clear();
        memset( dis, INF ,sizeof(dis) ) ;
        cnt = 0 ;
        res = 0 ;
        scanf("%d%d%d",&n,&m,&s);
        while( m-- ){
            Node tmp ;
            scanf("%d%d%d",&tmp.to,&x,&tmp.w);
            v[x].push_back(tmp);
        }
        bfs();
        for ( int i = 1 ; i <= n ; i++ ){
            if( dis[i] < INF ){
                cnt ++;
                res = max( res , dis[i] );
            }
        }
        cout << cnt << ' ' << res << endl; 
    }
    return 0 ;
}

D. Lift Problems
题目链接:https://nanti.jisuanke.com/t/28204
题意:电梯从0层往上走到n(不回头),1-n层每层都有要下电梯的人(可能为0),如果自己下面层的人下电梯,电梯停了愤怒就会增加1,如果跳过自己这层,往上走,则每走一层愤怒+1,直到电梯停止(愤怒停止增加),自己走回去(一直看错以为电梯拐回来。。。同学!没骨气啊,等电梯给你送回去呀!!)

思路:
dp,dp[i]表示到第i层停下的最小愤怒度。
用sum记录比自己去更高层的人数,初始电梯在0层时为总人数。
从1-n循环,每次将sum减少当前层人数,枚举i层前面所有层作为电梯前一个停的层数,找出停在i层时的最小愤怒度( j到i跳过的愤怒度+停下时要上更高层的所有人的愤怒度 )。
最后dp[n]即为所求。
Code:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int AX = 2e3+66;
int a[AX];
int dp[AX];
int main(){
    int T;
    scanf("%d",&T);
    int n ;
    while( T-- ){
        int sum = 0 ;
        scanf("%d",&n);
        for( int i = 1 ; i <= n ; i++ ){
            scanf("%d",&a[i]);
            sum += a[i];
        }
        dp[0] = 0 ;
        for( int i = 1; i <= n ; i++ ){
            sum -= a[i];
            int skip = 0;
            dp[i] = INF;
            for( int j = i - 1 ; j >= 0 ; j-- ){
                dp[i] = min( dp[i] , dp[j] + skip + sum );
                skip += (i-j)*a[j];
            }
        }
        printf("%d\n",dp[n]);
    }
    return 0 ;
}


E. Pawns
题目链接:https://nanti.jisuanke.com/t/28205
题意:每列有一个B,一个W,B在W上面,并且每次只能向对方的方向移动一个单位,但是如果B,W在边缘可以一次移动两个单位,W先手移动,问最后谁赢。
思路:待更
Code:

#include <bits/stdc++.h>
using namespace std;
const int AX = 20+6;
char G[AX][AX];
int main(){
    int T;
    scanf("%d",&T);
    int n , m ; 
    while( T-- ){
        scanf("%d%d",&n,&m);
        for( int i = 0 ; i < n ; i++ ){
            scanf("%s",G[i]);
        }
        int i = 0 ; 
        int w , b;
        int move = 0 ;
        int dou = 0 , white = 0 , black = 0 ;
        for( int j = 0 ; j < m ; j++ ){
            for( int k = 0 ; k < n ; k++ ){
                if( G[k][j] == 'B' ) b = k ;
                if( G[k][j] == 'W' ) w = k ;
            }
            if( b == 0 && w == n - 1 && n > 3 ) dou++;
            else{
                if( w == n - 1 && b < n - 3 ) white += n - 3 - b ;
                else if( b == 0 && w > 2 ) black += w - 2 ;
                move += w - b - 1;
            }
        }
        if( n == 4 && dou % 2 || white > black || ( white >= black - 1 && move % 2 ) ){
            printf("White wins\n");
        }else printf("Black wins\n");
    }
    return 0 ;
}

F. Runway Planning
题目链接:https://nanti.jisuanke.com/t/28206
签到题不多说
Code:

#include <bits/stdc++.h>
using namespace std;
int main(){
    int T;
    cin >> T;
    int n ;

    while( T-- ){
        cin >> n;
        int t = n / 10 + ( n % 10 >= 5 ) ;
        if( t > 18 ){
            t %= 18;
        }
        if( t == 0 ) t = 18;
        if( t < 10 ){
            cout << 0 << t << endl;
        }else{
            cout << t << endl;
        }
    }
    return 0 ;
}

H.Talent Selection
题目链接:https://nanti.jisuanke.com/t/28208
题意:给出n个人,其中f个喜欢的人,n-f个不喜欢的人,并给出他们的初始分数,现在要s个人晋级,你有k个加分项必须全部分配,每个人一项,问如何才能让尽量多的喜欢的人晋级。
思路:
二分答案key:
晋级s人,肯定要把高分先加给f个喜欢的人(分高的加的低),然后加给n-f的加。
假设已经给key个喜欢的人,s-key个不喜欢的人加分,剩下的分要加给没有晋级的人,所以要统计这个最大值,如果这个最大值小于等于我们选中的key个喜欢的人加分后的值,则这个key可能为结果。
Code:


#include <bits/stdc++.h>
using namespace std;
const int AX = 1e5+66;
int a[AX];
int c[AX];

int main(){
    int T;
    scanf("%d",&T);
    while( T-- ){
        int n , s , f;
        scanf("%d%d%d",&n,&s,&f);
        for( int i = 0 ; i < n ; i++ ){
            scanf("%d",&a[i]);
        }
        int k ;
        scanf("%d",&k);
        for( int i = 0 ; i < k ; i++ ){
            scanf("%d",&c[i]);
        }

        sort( a , a + f );
        sort( a + f , a + n );

        int l = max( 0 , s - ( n - f ) ); //答案的边界
        int r = min( s , f ) + 1 ;

        while( l < r ){//二分答案
            int key = ( l + r ) >> 1 ;

            int val = a[n-1-(s-key)]; // 初始化为不喜欢的人中最大的未加分的分值
            //求除了key个喜欢的人和s-key个不喜欢的人,剩下的未加分的加上分数后的最大值
            for ( int i = 0; i < k - f - ( s - key ) ; i ++ ){
                val = max( val, a[ k - 1 - ( s - key ) - i ] + c[i] );
            }
            //如果val大于已经选择晋级的喜欢的人,说明key不是想要的结果
            int j ;
            for( j = 0 ; j < key && a[f-1-j] + (( k - key + j ) < 0 ? 0 : c[k-key+j] ) >= val ; j++ );

            if( j == key ){
                l = key + 1 ;
            }else r = key;
        }
        printf("%d\n",l-1);
    }
    return 0 ;
}

J. Word Search
题目链接:https://nanti.jisuanke.com/t/28210
题意:给出一个网格,里面都是大写字母,又给出一堆单词,如果每一个单词都能在网格里面找到(横着,竖着找,斜着找这8个方向都可以),且每个单词出现一次,标记单词所在的字母,并输出剩下没有标记的字母。如果有一个单词出现2次以上,输出ambiguous,如果有单词没有找到,输出no solution。
如果全部标记没有结果输出,“empty solution”
思路:
数据小,暴力枚举。
每输入一个单词,就在网格找首单词出现的位置(这里也可以用pair存一下每个字母出现的位置,减少枚举量),然后搜8个方向的单词。
这里需要注意的就是回文的情况,如果是回文,那么这个单词就会出现两次。
还有单词长度为1的情况,那就是8次。
Code:

#include <bits/stdc++.h>
using namespace std;
const int AX = 50+6;
char G[AX][AX];
int mark[AX][AX];
char s[300];
int dir[8][2] ={
    {1,0},
    {0,1},
    {0,-1},
    {-1,0},
    {1,1},
    {-1,-1},
    {1,-1},
    {-1,1}
};
int main(){
    int T;
    scanf("%d",&T);
    int q , n , m ;
    while( T-- ){
        scanf("%d%d%d",&q,&n,&m);
        for( int i = 0 ; i < n ; i++ ){
            scanf("%s",G[i]);
        }
        int f = 1;
        int amb = 0;
        bool palindrome ;

        memset( mark , 0 , sizeof(mark) );
        while( q-- ){
            scanf("%s",s);
            int len = strlen(s);
            int kk ;
            for ( kk = 0; kk < (len-1)/2 && s[kk] == s[len-1-kk]; kk++);
                palindrome = (kk == (len-1)/2);
            int cnt = 0 ;
            for( int x = 0 ; x < n ; x++ ){
                for( int y = 0 ; y < m ; y ++ ){
                    int k ;
                    for( int d = 0 ; d < 8 ;  d++ ){
                        for( k = 0 ; k < len ; k++ ){
                            int xx = x + k * dir[d][0];
                            int yy = y + k * dir[d][1];
                            if( xx < 0 || xx >= n || yy < 0 || yy >= m || G[xx][yy] != s[k] ){
                                break;
                            }
                        }
                        if( k == len ){
                            cnt ++;
                            for( int j = 0 ; j < len ; j++ ){
                                int xx = x + j * dir[d][0];
                                int yy = y + j * dir[d][1];
                                mark[xx][yy] = 1 ;
                            }
                        }
                    }
                }
            }
            if( !cnt ){
                f = 0;
            }
            if (!( cnt == 1 || (palindrome && cnt == 2) || (len == 1 && cnt == 8)))
                amb = 1;
        }

        if( !f ){
            printf("no solution\n");
        }
        else if( amb ){
            printf("ambiguous\n");
        }else{
            int falg = 0 ;
            for( int i = 0 ; i < n ; i++ ){
                for( int j = 0 ; j < m ; j++ ){
                    if( !mark[i][j] ){
                        falg = 1;
                        printf("%c",G[i][j]);
                    }
                }
            }
            if(falg) printf("\n");
            else{
                printf("empty solution\n");
            }
        }
    }
    return 0 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值