2019年第十屆藍橋杯C/C++A組省賽 題面&部分題解

Table of Contents

前言

題目&做法

第一題

題意:

做法:

代碼:

總結:

第二題

題意:

思路:

代碼:

總結:

第三題

題意:

思路:

總結:

第四題

題意:

思路:

代碼:

總結:

第五題

題意:

思路:

代碼:

總結:

第六題

題意:

思路:

代碼:

總結:

第七題

題意:

思路:

代碼:

總結:

第八題

題意:

思路:

代碼:

總結:

第九題

題意:

思路:

代碼:

總結:

第十題

題意:

思路:

代碼:

總結:

比賽總結:


前言

        首先聲明,本篇博客爲根據對賽時的回憶所寫,相對於“題解”,其實更偏向於“總結”,總結賽時的機智和失誤。本博客提到的做法均不一定正確,請帶着批判的眼光來讀,發現問題歡迎評論與我討論。

題目&做法

第一題

題意:

        貌似是求1~2019的數裏含有'2','0','1','9'的數的平方和?

做法:

        C++11有to_string,把每個數轉成字符串之後判斷每一位是否爲那些數,如果是,加到平方和。

代碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 2019;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}
const char tmp[] = {'2','0','1','9'};
int main(){
    redirect();
    ll sum = 0;
    for(int i = 1;i <= maxn;i++){
        string str = to_string(i);
        for(int j = 0;str[j];j++)
        for(int k = 0;k < 4;k++)
        if(str[j] == tmp[k]){
            sum += i*i;
            goto label;
        }
        label:
            continue;
    }
    printf("%lld\n",sum);
    return 0;
}

總結:

        賽後寫的代碼,跟賽時寫得不太一樣。。我記得我沒有寫label。。那麼我賽時可能5分送分題寫掛了?可我記得樣例過了啊。。不太清楚,可能是記錯題了。

第二題

題意:

        貌似是類斐波那契數列?只不過遞推式改爲了f[i] = f[i-1] + f[i-2] + f[i-3]。只問f[20190324]最後四位。

思路:

        直接遞推就可以,數又不大。每次求完對10000取模即可,n再大考慮矩陣快速冪?

代碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = 10000;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}
int f[20190325];
int main(){
    redirect();
    f[1] = f[2] = f[3] = 1;
    for(int i = 4;i <= 20190324;i++)f[i] = (f[i-1] + f[i-2] + f[i-3])%mod;
    printf("%d\n",f[20190324]);
    return 0;
}

總結:

        答案4659,忘記我答案多少了。。希望我沒有從f[0]開始而多跑一位。。。

第三題

題意:

        把1~49分成7組,求每一組中位數,7箇中位數再求一箇中位數,問最終結果最大多少。

思路:

        貪心,一定比這個中位數大的就讓比它大,剩下的都比它小就好。這個答案應該在分組的第四組,一定比它大的包括第五、六、七組中位數以及大於這些中位數的數:

(1,1) (1,2) (1,3) (1,4) (1,5) (1,6) (1,7)
(2,1) (2,2) (2,3) (2,4) (2,5) (2,6) (2,7)
(3,1) (3,2) (3,3) (3,4) (3,5) (3,6) (3,7)
(4,1) (4,2) (4,3) (4,4) (4,5) (4,6) (4,7)
(5,1) (5,2) (5,3) (5,4) (5,5) (5,6) (5,7)
(6,1) (6,2) (6,3) (6,4) (6,5) (6,6) (6,7)
(7,1) (7,2) (7,3) (7,4) (7,5) (7,6) (7,7)

        我們要讓(4,4)最大,比它大的有(4,5)~(4,7) (5,4)~(5,7) (6,4)~(6,7) (7,4)~(7,7)共有3*4+3個數,49-(3*4+3)=34

總結:

        賽場上智障了,莫名其妙以爲(5,4)是要求的答案,求出來個結果38。涼了。

第四題

題意:

        給一個迷宮,求在步數最小的情況下,取行動方案連成的字典序最小的字符串。

思路:

        按字典序排每一次試探的行動順序,然後就是bfs走迷宮模板題,將步數變成字符串就行。

代碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);
static const char ch[] = {'D','L','R','U'};
static const int dx[] = {1,0,0,-1};
static const int dy[] = {0,-1,1,0};

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}

struct node{
    int x,y;
    string z;
    node(int a,int b,string c){
        x = a,y = b,z = c;
    }
    node(){}
};

int maze[100][100];
bool vis[100][100];
int n = 30,m = 50;
int sx,sy,gx,gy;

int main(){
    redirect();
    for(int i = 0;i < n;i++)
    for(int j = 0;j < m;j++)
    scanf("%1d",&maze[i][j]);
    gx = n-1,gy = m-1;
    queue<node>q;
    q.push(node(0,0,""));
    while(!q.empty()){
        node p = q.front();
        q.pop();
        vis[p.x][p.y] = true;
        if(p.x == gx && p.y == gy){
            cout << p.z << endl;
            return 0;
        }
        for(int i = 0;i < 4;i++){
            int mx = p.x+dx[i],my = p.y+dy[i];
            if(mx >= 0 && mx < n && my >= 0 && my < m && !vis[mx][my] && !maze[mx][my])
            q.push(node(mx,my,p.z+ch[i]));
        }
    }
    return 0;
}

總結:

        怎麼現在回憶pdf上給的字典序排序是D<U<L<R?這不對呀。。(突然背後一絲涼意。。。)

第五題

題意:

        RSA加密,給定p,q兩個質數,n=p*q,再給一個與(p-1)*(q-1)互質的d,就能找到一個e使得d*e%(p-1)(q-1) = 1

        給定密文C,可根據C^e%n得出原文D

思路:

        暴力i從2到sqrt(n),如果n%i==0,輸出i,n/i 很快就跑出來了p和q,驗算一下(p-1)(q-1)與d確實是互質的。設a = (p-1)(q-1)

則存在i∈[0,d),使(i*a+1)%d == 0,此時(i*a+1)/d即爲e,然後快速冪取模,pow(C,e,n)即可得出答案。

代碼:

求p,q,a部分:

bool isPrime(ll n){
    ll x = sqrt(n);
    for(ll i = 2;i <= x;i++)
    if(n%i == 0)return false;
    return true;
}
int main(){
    ll n = xxx,d = yyy;
    ll maxn = sqrt(n);
    for(ll i = 2;i <= maxn;i++)
    if(n % i == 0){
        ll p = i,q = n/i,a = (p-1)*(q-1);
        if(isPrime(p) && isPrime(q) && __gcd(a,d)==1)printf("%lld %lld %lld\n",p,q,a);
    }
    return 0;
}

求答案部分:

a = int(xxx)
n = int(yyy)
d = int(zzz)
C = int(???)
e = 0
for i in range(0,d):
    if (i*a+1)%d == 0:
	    e = (i*a+1)/d
print(pow(C,int(e),n))

總結:

        數論不好,如果沒有python恐怕要用__int128來做後半部分了。。還好這次給了python。。求出來的答案記得是18B,開頭字母爲8,正不正確還不知道。。。

第六題

題意:

        給一個完全二叉樹,和每個點的值,問第幾層值之和最大。

思路:

        完全二叉樹≠滿二叉樹。求一個完全二叉樹有幾層要看給的n(節點個數)的二進制最高位是第幾位。比如1的最高位爲第一位,只有一層;7(111)的最高位爲第三位,有三層,8(1000)的最高位爲第四位,有四層……對每一層求個和,和先前記錄的最大值比較一下,大了更新即可。

代碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const ll INF = 0x3f3f3f3f3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}

int main(){
    redirect();
    int n,x,depth = 0;
    scanf("%d",&n);
    x = n;
    while(x)x>>=1,depth++;
    ll maxval = -INF,ans;
    for(int i = 1;i <= depth;i++){
        ll sum = 0;
        int cnt = 1<<(i-1);
        while(cnt-- && ~scanf("%d",&x))sum += x;
        if(sum > maxval)maxval = sum,ans = i;
    }
    printf("%I64d\n",ans);
    return 0;
}

總結:

        這題其實是有坑點的,首先不是滿二叉樹。。最後一層不一定是滿的。其次結點值可能是負的,那些取maxval = 0的涼了。還有這題好像爆int,要開long long。

第七題

題意:

        有n個商家,初始熱度均爲0,他們在時間T分鐘內總共產生m個訂單。對一個商家,如果某分鐘沒接到訂單,熱度將-1(掉到0爲止不會再掉),每接到一個訂單,熱度+2,且這一分鐘熱度不會-1。對平臺來說,某個商家熱度到達6,將加入到一個名單上,當某商家熱度掉到3,將從名單上劃掉,問在時間T,處理完m個訂單之後,在名單上的商家個數。

思路:

        對訂單集合按時間sort一下,每分鐘將每個商家熱度-1是不現實的,否則將會是O(n*T)複雜度,也就拿個暴力分了。

        一個很自然的想法是,對於每個商家,開個last數組記錄上一次接到訂單是什麼時候,以便在下一次接單的時候能夠方便地計算出兩次接單的時間差,在此期間掉的熱度就知道了。

代碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}

struct node{
    int t,x;    
}p[maxn];

bool operator <(const node& a,const node& b){
    return a.t < b.t;
}

int last[maxn];
int val[maxn];
bool live[maxn];

int main(){
    redirect();
    int n,m,T,cnt = 0;
    scanf("%d %d %d",&n,&m,&T);
    for(int i = 1;i <= m;i++)scanf("%d %d",&p[i].t,&p[i].x);
    sort(p+1,p+1+m);
    for(int i = 1;i <= m;i++){
        int &t = p[i].t,&x = p[i].x;
        val[x] = max(0,val[x]-max(0,t-last[x]-1)) + 2;
        last[x] = t;
        if(val[x] > 5)live[x] = true;
        else if(val[x] <= 3)live[x] = false;
    }
    for(int i = 1;i <= n;i++){
        val[i] -= T-last[i];
        if(val[i] <= 3)live[i] = false;
        if(live[i])cnt++;
    }
    printf("%d\n",cnt);
    return 0;
}

總結:

        同樣卡了很久。。而且不知道正確性。。。而且。。最關鍵的是。。這題叫“飽了麼”。。。都給沒吃早餐的我給看餓了。

第八題

題意:

        給定一個數組a[],從頭到尾,對於每個數a[i],如果在i位置之前出現過,就一直+1,直到沒有在先前的數組裏出現過,這成爲新的a[i]

思路:

        直接暴力O(n^2)顯然只能拿部分分。考慮到a[i]大小最大隻有1e6,可以開一個set,存入1~1e6(如果每個數都是1e6,而有1e5個,最大的數可到1.1e6-1),每次給一個數x,查詢set.lower_bound(x)代表的數,給這個位置就行了,然後再set裏把這個數刪掉。複雜度O(n*logm)(m爲集合裏數的數量)

代碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("out.txt","r",stdin);
        freopen("ans.txt","a+",stdout);
    #endif
}

set<int>s;
inline int read(){
    int num = 0,w = 0;
    char ch = 0;
    while(!isdigit(ch)){
        w |= ch == '-';
        ch = getchar();
    }
    while (isdigit(ch)){
        num = (num<<3) + (num<<1) + (ch^48);
        ch = getchar();
    }
    return w? -num: num;
}

int main(){
    redirect();
    for(int i = 1;i < 1100000;i++)s.insert(i);
    int n,x;
    scanf("%d",&n);
    for(int i = 1;i <= n;i++){
        x = read();
        set<int>::iterator it = s.lower_bound(x);
        printf("%d%c",*it,i==n?'\n':' ');
        s.erase(it);
    }
    return 0;
}

總結:

        Process exited after 1.019 seconds with return value 0

        這是上述代碼運行極端數據(1e5個1e6)的結果。時間1s有點兒緊。。能不能過還要看運氣。不過我這道題是過不了了。。想複雜腦抽寫了並查集,記錄一段連續區間的區間首,在區間首記錄區間長度……這思路根本就不對- -算法是fake的,都怪當時太緊張,測了下樣例過了,自己出了個n=10的隨機數據過了就給交了。。

        另外有思路採用樹狀數組+二分,跟set在時間上應該差不了多少。

第九題

題意:

        有n袋糖,裏面有m種糖,每袋糖有k個,問至少取幾包才能攢夠所有種類的糖。

        n <= 100;m,k <= 20

思路:

        莫名感覺很像區間覆蓋問題,假如一袋糖覆蓋一個區間的話。。。後來馬上否認了,一袋糖裏裝的糖的種類是隔開而不連續的。。

        沒有太好的辦法,看到數據量決定還是dfs鋌而走險。記錄每袋糖擁有的糖果種類,同時記錄哪些袋種有某種糖果種類(相互映射關係),對糖果種類dfs暴搜+剪枝。

代碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 100010;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);

void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}
//s1爲某袋糖果裏有的糖果種類,s2爲某種類哪些袋子裏有
set<int>s1[110],s2[25];
int n,m,k,x;
bool a[110];//選哪些袋
int ans = INF;
void dfs(int x){
    if(x > m){
        int cnt = 0;
        for(int i = 1;i <= n;i++)
        if(a[i])cnt++;
        ans = min(ans,cnt);
        return;
    }
    for(int i = 1;i <= n;i++)
    if(a[i] && s1[i].count(x))dfs(x+1);
    for(set<int>::iterator it = s2[x].begin();it != s2[x].end();it++){
        a[*it] = true;
        dfs(x+1);
        a[*it] = false;
    }
}
int main(){
    redirect();
    scanf("%d %d %d",&n,&m,&k);
    for(int i = 1;i <= n;i++)
    for(int j = 1;j <= k;j++){
        scanf("%d",&x);
        s1[i].insert(x);
        s2[x].insert(i);
    }
    dfs(1);
    printf("%d\n",ans==INF?-1:ans);
    return 0;
}

總結:

        太brute force了。。不一定能AC

第十題

題意:

        給定T,k,其中T爲樣例數,k爲質數,對於每組樣例,給出n,m,求

        \small \sum_{i=1}^{n}\sum_{j=0}^{min(i,m)}(C(i,j)|k)

思路:

        只會根據C(i,j)=C(i-1,j) + C(i-1,j-1)遞推求n<=2000時的少組樣例,甚至連莫隊算法都沒法用。

代碼:

        太brute force了,略了。

總結:

        thu2016集訓原題,看來出題人要麼是thu老師,要麼是thu巨巨,要麼是有淵源的狼滅吧。

比賽總結

        如果說去年不能進省一是有可能,而因爲運氣好進了,今年恐怕真就懸了,懸的是做題時狀態的飄忽不定,懸的是不認真讀題,各種腦抽,懸的是太過重視而壓力太大。實際上,去年賽前壓根沒想過會拿省一,就放下包袱輕裝上陣,結局正出人意料。

        賽已完,只能夠祈福主辦方能給我一次北京的機會了。

                                                                                                                                                            2019-3-24 21:52於hhu

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章