11界蓝桥杯省赛第二场 C++ B组

试题A: 门牌制作

\(1\sim 2020\) 中包含多少个字符 \(2\)

暴力枚举。

#include <cstdio>

int getCnt(int x)
{
    int cnt = 0;
    while (x) {
        if (x%10 == 2) ++cnt;
        x /= 10;
    }
    return cnt;
}
int main()
{
    int ans = 0;
    for (int i = 1; i <= 2020; ++i) {
        ans += getCnt(i);
    }
    printf("%d", ans);  // 624
    return 0;
}

试题B: 既约分数

有多少个 \(\frac ba(a,b\in N_+\and 1\le a,b\le2020)\) 满足 \(gcd(a,b)=1\)

暴力枚举。

#include <cstdio>

int gcd(int a, int b)
{
    while (b) {
        int t = a%b;
        a = b;
        b = t;
    }
    return a;
}
int main()
{
    int ans = 0;
    for (int a = 1; a <= 2020; ++a) {
        for (int b = 1; b <= 2020; ++b) {
            if (gcd(a,b) == 1) ++ans;
        }
    }
    printf("%d", ans);  // 2481215
    return 0;
}

试题C: 蛇形填数

数学求解: \(ans=1+2+\cdots+38+20=\frac{1+38}{2}\times 38+20=761\) .

或者直接模拟。

#include <cstdio>

int num[41][41];
int main()
{
    int t = 0;
    for (int k = 2; k <= 40; ++k) {
        for (int i = 1; i < k; ++i) {  // 不妨认为每次都是从左下向右上填数,结果一样
            int j = k-i;
            num[i][j] = ++t;
        }
    }
    printf("%d", num[20][20]);  // 761
    return 0;
}

试题D: 跑步锻炼

模拟日期变化。

#include <cstdio>

struct Date
{
    int year, month, day;
    Date operator ++ ()
    {
        int mdays[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
        if (year%400 == 0 || year%4 == 0 && year%100 != 0) mdays[2] = 29;
        ++day;
        if (day > mdays[month]) {
            day = 1; ++month;
            if (month > 12) month = 1, ++year;
        }
    }
    bool operator != (Date t) const
    {
        return year != t.year || month != t.month || day != t.day;
    }
};
int main()
{
    Date date = {2000,1,1};
    int d = 6;
    int ans = 0;
    for (; date != Date{2020,10,2}; ++date, d = (d+1)%7) {
        ++ans;
        if (d == 1 || date.day == 1) {
            ++ans;
        }
    }
    printf("%d", ans);  // 8879
    return 0;
}

试题E: 七段码

求七段码数码管的所有发光数码管连成一片的情况的种类数。

暴力枚举或者手算应该都可以,感觉比较麻烦。

可以将数码管看成有 \(6\) 个顶点, \(7\) 条边 \(a,b,c,d,e,f,g\) 构成的图,容易想到发光数码管连成一片等价于一个集合。可以用并查集解决。

/*
6顶点,7条边
1-2
| |
6-3
| |
5-4
*/



#include <cstdio>
#include <vector>
using namespace std;

/**
 * Disjoint Set Union
 */
class DSU
{
    vector<int> s;
 public:
    DSU(int n) {
        s.resize(n+1);
        for (int i = 1; i <= n; ++i) s[i] = i;
    }
    int find(int x) {
        if (x != s[x]) s[x] = find(s[x]);
        return s[x];
    }
    bool unite(int u, int v) {
        int x = find(u);
        int y = find(v);
        if (x != y) {
            s[y] = x;
            return true;
        }
        return false;
    }
};
int main()
{
    int ans = 0;
    for (int state = 0; state < (1<<7); ++state) {
        int x = state;
        DSU dsu(6);
        for (int i = 0; i < 7; ++i) {
            int t = x&1;
            int u = i,v = (i+1)%6;
            if (i == 6) u = 2, v = 5;
            ++u, ++v;
            if (t) dsu.unite(u,v) ;
            x >>= 1;
        }
        int t = -1;
        for (int i = 1; i <= 6; ++i) {
            int x = dsu.find(i);
            if (x == i) continue;
            if (t == -1) t = x;
            else if (t != x) {
                t = -1;
                break;
            }
        }
        if (t != -1) {
            ++ans;
        }
    }
    printf("%d", ans);  // 80
    return 0;
}

试题F: 成绩统计

#include <cstdio>

int main()
{
    int n;
    scanf("%d", &n);
    int ok = 0, great = 0;
    for (int i = 0; i < n; ++i) {
        int x;
        scanf("%d", &x);
        if (x >= 60) ++ok;
        if (x >= 85) ++great;
    }
    printf("%.0f%%\n%.0f%%", 100.0*ok/n, 100.0*great/n);
    return 0;
}

试题G: 回文日期

枚举年份即可枚举所有回文日期。

#include <cstdio>

int rev(int n)
{
    int x = 0;
    while (n) {
        x *= 10;
        x += n%10;
        n /= 10;
    }
    return x;
}
int main()
{
    int n;
    scanf("%d", &n);
    int ans1 = 0, ans2 = 0;
    for (int i = n/10000; ; ++i) {
        int t = i*10000+rev(i);
        if (t <= n) continue;
        if (ans1 == 0) ans1 = t;
        if (i%100 == i/100) {
            ans2 = t;
            break;
        }
    }
    printf("%d\n%d\n", ans1, ans2);
    return 0;
}

试题H: 子串分值和

维护以 \(j\) 为尾的所有子串的分值,可以推出以 \(j+1\) 为尾的所有子串的分值。

用线段树维护,时间复杂度 \(O(n\log n)\)

#include <cstdio>
#include <vector>
using namespace std;

typedef long long LL;

class SegTree
{
    vector<LL> tree, lazy;
    void pushUp(int rt) {
        tree[rt] = tree[rt<<1] + tree[rt<<1|1];
    }
    void pushDown(int L, int R, int rt) {
        if (L == R || !lazy[rt]) return;
        LL &t = lazy[rt];
        int m = L+R >> 1;
        tree[rt<<1] += t*(m-L+1);
        tree[rt<<1|1] += t*(R-m);
        lazy[rt<<1] += t;
        lazy[rt<<1|1] += t;
        t = 0;
    }
public:
    SegTree(int n) {
        tree.resize(n<<2);
        lazy.resize(n<<2);
    }
    void update(int x, int y, LL val, int L, int R, int rt){
        if (x <= L && R <= y) {
            tree[rt] += val*(R-L+1);
            lazy[rt] += val;
            return;
        }
        pushDown(L, R, rt);
        int m = L+R >> 1;
        if (x <= m) update(x, y, val, L, m, rt<<1);
        if (y > m) update(x, y, val, m+1, R, rt<<1|1);
        pushUp(rt);
    }
    LL query(int x, int y, int L, int R, int rt) {
        if (x <= L && R <= y) {
            return tree[rt];
        }
        pushDown(L, R, rt);
        int m = L+R >> 1;
        LL ans = 0;
        if (x <= m) ans += query(x, y, L, m, rt<<1);
        if (y > m) ans += query(x, y, m+1, R, rt<<1|1);
        return ans;
    }
};

const int N = 1e5+5;
char s[N];
int pos[128];
int main()
{
    scanf("%s", s+1);
    int n = 0;
    for (int i = 1; s[i]; ++i) ++n;
    SegTree st(n);
    LL ans = 0;
    for (int i = 1; s[i]; ++i) {
        int L = pos[s[i]]+1;
        st.update(L, i, 1, 1, n, 1);
        ans += st.query(1, n, 1, n, 1);
        pos[s[i]] = i;
    }
    printf("%lld", ans);
    return 0;
}

试题I: 平面切分

\(hint:\) 平面上每增加一条直线,若该直线上有 \(k\) 个交点,则新增区域 \(k+1\) 个。

为避免浮点误差,定义 Fraction 类表示分数。

首先将所有直线排序后去重,时间复杂度 \(O(n\log n)\)

然后依次在平面上添加直线,求交点个数,记录交点的横坐标即可,交点坐标需要排序后去重,总时间复杂度 \(O(n^2\log n)\)

#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;

typedef long long LL;

class Fraction
{
    LL s, m;
    LL gcd(LL a, LL b) {
        while (b) {
            LL r = a%b;
            a = b;
            b = r;
        }
        return a;
    }
public:
    Fraction(){}
    Fraction(LL son, LL mom) : s(son), m(mom) {
        LL d = gcd(s, m);
        if (d < 0) d = -d;
        s /= d, m /= d;
        if(m < 0) s = -s;
    }
    bool operator < (const Fraction &f) const {
        return s*f.m < m*f.s;
    }
    bool operator == (const Fraction &f) const {
        return s == f.s && m == f.m;
    }
};

const int N = 1003;
struct P
{
    LL a, b;
    bool operator < (const P &t) const
    {
        if (a != t.a) return a < t.a;
        return b < t.b;
    }
    bool operator == (const P &t) const
    {
        return a == t.a && b == t.b;
    }
}p[N];
Fraction dot[N];
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
        scanf("%lld%lld", &p[i].a, &p[i].b);
    }
    sort(p, p+n);
    n = unique(p, p+n) - p;
    int ans = 1;
    for (int i = 0; i < n; ++i) {
        int k = 0;
        for (int j = 0; j < i; ++j) {
            if (p[i].a == p[j].a) continue;
            dot[k++] = Fraction(p[i].b-p[j].b, p[j].a-p[i].a);
        }
        sort(dot, dot+k);
        k = unique(dot, dot+k)-dot;
        ans += k+1;
    }
    printf("%d", ans);
    return 0;
}

试题J: 字串排序

看别人题解之前根本没想到动态规划。感觉有点类似数位 \(dp\)

冒泡排序交换次数即逆序对个数。

长度为 \(n\) 的字符串降序排列时逆序对最多,为 \(1+2+\cdots+n-1=\frac{n(n-1)}2\) 个。

不难发现:满足 \(\frac{n(n-1)}2>=V\) 最小的 \(n\) 即为所求字符串长度。

\(f_{i,j,k}\) 为长度为 \(i\) 开头连续 \(k\) 个字符为 \(j\) 的字符串的最大逆序对个数。

\[f_{i,j,k}= \begin{cases} 0&i=1 \\ \max_{k=1}^{i}(f_{i-1,j-1,k}+i-1)&i>1\and k=1\\ f_{i-1,j,k-1}+i-k&i>1\and k>1 \end{cases} \]

从高位往地位遍历,按字典序从小到大遍历,即可找到字典序最小解。

#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;

const int N = 144;
int f[N][26][N];  // f[i][j][k]代表长度为i首k个字母为'a'+j的字符串的最大逆序对个数
void init()
{
    for (int i = 2; i < N; ++i) {
        for (int j = 1; j < 26; ++j) {
            for (int k = 1; k < i; ++k) {
                f[i][j][1] = max(f[i][j][1], f[i-1][j-1][k]+i-1);
            }
            for (int k = 2; k < i; ++k) {
                f[i][j][k] = max(f[i][j][k], f[i-1][j][k-1]+i-k);
            }
        }
    }
}
int main()
{
    init();
    int V;
    scanf("%d", &V);
    int n = 2;
    for (; n*(n-1)/2 < V; ++n);  // V == 10000 时 n == 142
    int tmp = 0;
    for (int i = n; i >= 1;) {
        int j, k;
        for (j = 0; j < 26; ++j) {
            for (k = 1; k <= i; ++k) {
                if (f[i][j][k] >= V) goto outer;
            }
        }
        outer:;
        i -= k; V -= i*k;
        while (k--) putchar(j+'a');
    }
    return 0;
}

posted @ 2021-03-05 20:33  Zewbie  阅读(104)  评论(0)    收藏  举报